@designbasekorea/ui-wc 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/LICENSE-WORLD-MAP.md +10 -0
- package/README.md +58 -0
- package/dist/esm/components/db-achievement-list.d.ts +2 -0
- package/dist/esm/components/db-achievement-list.js +1 -1
- package/dist/esm/components/db-activity-heatmap.d.ts +1 -0
- package/dist/esm/components/db-activity-heatmap.js +1 -1
- package/dist/esm/components/db-ad-banner.d.ts +2 -0
- package/dist/esm/components/db-ad-banner.js +1 -1
- package/dist/esm/components/db-ai-chat-list.d.ts +5 -1
- package/dist/esm/components/db-ai-chat-list.js +1 -1
- package/dist/esm/components/db-ai-chat.d.ts +2 -0
- package/dist/esm/components/db-ai-chat.js +1 -1
- package/dist/esm/components/db-ai-file-tree.js +1 -1
- package/dist/esm/components/db-alert.d.ts +1 -0
- package/dist/esm/components/db-alert.js +1 -1
- package/dist/esm/components/db-audio-player.d.ts +2 -0
- package/dist/esm/components/db-audio-player.js +1 -1
- package/dist/esm/components/db-avatar.d.ts +1 -0
- package/dist/esm/components/db-avatar.js +1 -1
- package/dist/esm/components/db-banner.d.ts +2 -0
- package/dist/esm/components/db-banner.js +1 -1
- package/dist/esm/components/db-button.d.ts +7 -1
- package/dist/esm/components/db-button.js +1 -1
- package/dist/esm/components/db-calendar.d.ts +5 -0
- package/dist/esm/components/db-calendar.js +1 -1
- package/dist/esm/components/db-card.d.ts +1 -0
- package/dist/esm/components/db-card.js +1 -1
- package/dist/esm/components/db-chat-composer.d.ts +3 -0
- package/dist/esm/components/db-chat-composer.js +1 -1
- package/dist/esm/components/db-chat-message.d.ts +2 -0
- package/dist/esm/components/db-chat-message.js +1 -1
- package/dist/esm/components/db-chat-room.d.ts +1 -0
- package/dist/esm/components/db-chat-room.js +1 -1
- package/dist/esm/components/db-chat-typing.d.ts +2 -0
- package/dist/esm/components/db-chat-typing.js +1 -1
- package/dist/esm/components/db-chat-widget.d.ts +9 -0
- package/dist/esm/components/db-chat-widget.js +1 -1
- package/dist/esm/components/db-chip.d.ts +2 -0
- package/dist/esm/components/db-chip.js +1 -1
- package/dist/esm/components/db-code-block.d.ts +1 -0
- package/dist/esm/components/db-code-block.js +1 -1
- package/dist/esm/components/db-color-picker.d.ts +4 -0
- package/dist/esm/components/db-color-picker.js +1 -1
- package/dist/esm/components/db-color-swatch.d.ts +3 -0
- package/dist/esm/components/db-color-swatch.js +1 -1
- package/dist/esm/components/db-cookie-banner.d.ts +3 -0
- package/dist/esm/components/db-cookie-banner.js +1 -1
- package/dist/esm/components/db-countdown.d.ts +2 -1
- package/dist/esm/components/db-countdown.js +1 -1
- package/dist/esm/components/db-date-picker.d.ts +3 -0
- package/dist/esm/components/db-date-picker.js +1 -1
- package/dist/esm/components/db-dropdown.d.ts +1 -0
- package/dist/esm/components/db-dropdown.js +1 -1
- package/dist/esm/components/db-dropzone.d.ts +2 -0
- package/dist/esm/components/db-dropzone.js +1 -1
- package/dist/esm/components/db-email-verify.d.ts +1 -0
- package/dist/esm/components/db-email-verify.js +1 -1
- package/dist/esm/components/db-file-uploader.d.ts +2 -0
- package/dist/esm/components/db-file-uploader.js +1 -1
- package/dist/esm/components/db-form.d.ts +2 -0
- package/dist/esm/components/db-form.js +1 -1
- package/dist/esm/components/db-gantt.d.ts +2 -0
- package/dist/esm/components/db-gantt.js +1 -1
- package/dist/esm/components/db-image-list.d.ts +3 -0
- package/dist/esm/components/db-image-list.js +1 -1
- package/dist/esm/components/db-image.d.ts +1 -0
- package/dist/esm/components/db-image.js +1 -1
- package/dist/esm/components/db-input-group.d.ts +30 -0
- package/dist/esm/components/db-input-group.js +1 -0
- package/dist/esm/components/db-input.d.ts +1 -0
- package/dist/esm/components/db-input.js +1 -1
- package/dist/esm/components/db-kanban.d.ts +3 -0
- package/dist/esm/components/db-kanban.js +1 -1
- package/dist/esm/components/db-lightbox.d.ts +2 -0
- package/dist/esm/components/db-lightbox.js +1 -1
- package/dist/esm/components/db-list.d.ts +2 -0
- package/dist/esm/components/db-list.js +1 -1
- package/dist/esm/components/db-markdown-editor.d.ts +2 -0
- package/dist/esm/components/db-markdown-editor.js +1 -1
- package/dist/esm/components/db-menu-item.js +1 -1
- package/dist/esm/components/db-navbar.d.ts +3 -0
- package/dist/esm/components/db-navbar.js +1 -1
- package/dist/esm/components/db-node.d.ts +1 -0
- package/dist/esm/components/db-node.js +1 -1
- package/dist/esm/components/db-onboarding-modal.d.ts +2 -0
- package/dist/esm/components/db-onboarding-modal.js +1 -1
- package/dist/esm/components/db-otp-input.d.ts +4 -1
- package/dist/esm/components/db-otp-input.js +1 -1
- package/dist/esm/components/db-page-header.js +1 -1
- package/dist/esm/components/db-popover.d.ts +1 -0
- package/dist/esm/components/db-popover.js +1 -1
- package/dist/esm/components/db-progress-step.d.ts +2 -0
- package/dist/esm/components/db-progress-step.js +1 -1
- package/dist/esm/components/db-range-slider.d.ts +1 -0
- package/dist/esm/components/db-range-slider.js +1 -1
- package/dist/esm/components/db-rating.d.ts +1 -0
- package/dist/esm/components/db-rating.js +1 -1
- package/dist/esm/components/db-search-bar.d.ts +2 -0
- package/dist/esm/components/db-search-bar.js +1 -1
- package/dist/esm/components/db-section-logo-cloud.js +1 -1
- package/dist/esm/components/db-share.d.ts +5 -0
- package/dist/esm/components/db-share.js +1 -1
- package/dist/esm/components/db-sidebar.d.ts +3 -0
- package/dist/esm/components/db-sidebar.js +1 -1
- package/dist/esm/components/db-signature.d.ts +2 -0
- package/dist/esm/components/db-signature.js +1 -1
- package/dist/esm/components/db-spinner.d.ts +2 -0
- package/dist/esm/components/db-spinner.js +1 -1
- package/dist/esm/components/db-stepper.d.ts +2 -0
- package/dist/esm/components/db-stepper.js +1 -1
- package/dist/esm/components/db-table.d.ts +2 -0
- package/dist/esm/components/db-table.js +1 -1
- package/dist/esm/components/db-tabs.d.ts +1 -0
- package/dist/esm/components/db-tabs.js +1 -1
- package/dist/esm/components/db-ticket.js +1 -1
- package/dist/esm/components/db-time-picker.d.ts +2 -0
- package/dist/esm/components/db-time-picker.js +1 -1
- package/dist/esm/components/db-timeline.d.ts +4 -0
- package/dist/esm/components/db-timeline.js +1 -1
- package/dist/esm/components/db-toast.d.ts +2 -0
- package/dist/esm/components/db-toast.js +1 -1
- package/dist/esm/components/db-tool-layout.d.ts +92 -0
- package/dist/esm/components/db-tool-layout.js +1 -0
- package/dist/esm/components/db-tool-panel.d.ts +50 -0
- package/dist/esm/components/db-tool-panel.js +1 -0
- package/dist/esm/components/db-top-banner.d.ts +2 -0
- package/dist/esm/components/db-top-banner.js +1 -1
- package/dist/esm/components/db-video-player.d.ts +3 -0
- package/dist/esm/components/db-video-player.js +1 -1
- package/dist/esm/components/db-world-map.d.ts +60 -0
- package/dist/esm/components/db-world-map.js +1 -0
- package/dist/esm/components/db-youtube-player.d.ts +2 -0
- package/dist/esm/components/db-youtube-player.js +1 -1
- package/dist/esm/components/db-zoom-control.d.ts +52 -0
- package/dist/esm/components/db-zoom-control.js +1 -0
- package/dist/esm/components/input-validation.js +1 -1
- package/dist/esm/components/paper-card.d.ts +2 -0
- package/dist/esm/components/paper-card.js +1 -1
- package/dist/esm/core/i18n-copy.js +1 -1
- package/dist/esm/core/i18n-en.js +1 -1
- package/dist/esm/core/i18n.d.ts +31 -1
- package/dist/esm/core/i18n.js +1 -1
- package/dist/esm/helpers/world-countries.js +1 -0
- package/dist/esm/helpers/world-land.js +1 -0
- package/dist/esm/index.d.ts +6 -1
- package/dist/esm/index.js +1 -1
- package/dist/esm/node_modules/@designbasekorea/ui-contracts/dist/components.js +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.css +1 -1
- package/dist/index.d.ts +442 -6
- package/dist/index.esm.js +1 -1
- package/dist/react/index.cjs +1 -1
- package/dist/react/index.d.ts +837 -71
- package/dist/react/index.esm.js +1 -1
- package/package.json +4 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# 변경 이력
|
|
2
2
|
|
|
3
|
+
## 0.11.0 - 2026-11-03
|
|
4
|
+
|
|
5
|
+
- 다크모드 중립 Tooltip의 글자·배경 대비와 화살표 표면 연결 수정.
|
|
6
|
+
- InputGroup 아이콘 버튼 예제: 좌측·우측·양쪽 액션, Tooltip 조합, s/m/l 정렬 및 실제 버튼 disabled 연결.
|
|
7
|
+
|
|
8
|
+
- InputGroup 추가: 좌우 Button·Select 결합, 공유 테두리/높이/포커스, 그룹 라벨·도움말·오류·disabled 및 네이티브 폼 동작 유지.
|
|
9
|
+
- ToolPanel 제목 우측 actions 슬롯 예제·스타일·가이드 추가. 폼 예제의 중복 DemoSection을 공통 헤더·본문 구조로 통일.
|
|
10
|
+
|
|
11
|
+
- Tools: `db-zoom-control` / `ZoomControl` 추가. 비율 메뉴·확대/축소·화면 맞춤·전체화면, 범위·단계 설정 및 캔버스에 한정된 키보드 단축키 제공. `xs` 24px, 기능 선택 `controls`, 단순 +/− `zoom-style="plain"` 지원.
|
|
12
|
+
- ToolButton 툴팁 trigger의 기준선 차이로 아이콘·텍스트 버튼의 수직 중심이 달라지던 문제 수정.
|
|
13
|
+
- 다크모드에서 `tone="dark"` DesignToolbar의 배경과 아이콘 대비 수정.
|
|
14
|
+
|
|
3
15
|
## 0.10.0 - 2026-10-29
|
|
4
16
|
|
|
5
17
|
- Navbar·PageHeader의 반응형 배치, sticky 레이어 및 메뉴 표시 개선.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# World map geographic data
|
|
2
|
+
|
|
3
|
+
The geographic outlines in `world-land.ts` derive from Natural Earth `ne_110m_land` (1:110m land polygons). Coordinates are rounded to three decimal places; the solid globe also includes simplified country boundaries from `ne_110m_admin_0_countries`. Boundaries are for visual context and do not express a political position. Maps are decorative geographic overviews, not navigation tools.
|
|
4
|
+
|
|
5
|
+
Source: https://github.com/nvkelso/natural-earth-vector/blob/master/geojson/ne_110m_land.geojson
|
|
6
|
+
Country data: https://github.com/nvkelso/natural-earth-vector/blob/master/geojson/ne_110m_admin_0_countries.geojson
|
|
7
|
+
Terms: https://www.naturalearthdata.com/about/terms-of-use/
|
|
8
|
+
Natural Earth geographic data is public domain. Made with Natural Earth.
|
|
9
|
+
|
|
10
|
+
The UI component and its projection/interaction code follow the ui-wc package license.
|
package/README.md
CHANGED
|
@@ -548,3 +548,61 @@ import { PageHeader, Select, SegmentControl, Sidebar } from '@designbasekorea/ui
|
|
|
548
548
|
```
|
|
549
549
|
|
|
550
550
|
`sticky`는 가장 가까운 스크롤 영역의 상단에 고정합니다. `compactOnScroll`은 원래 위치를 지나면 설명과 격자를 숨기고 제목·개수·컨트롤을 유지합니다. 상단 앱 바가 있으면 헤더의 `--db-page-header-sticky-offset`에 해당 높이(예: `64px`)를 지정하세요. 별도 자바스크립트 스크롤 핸들러는 필요하지 않습니다. `Sidebar`의 `navigation` 타입은 문서·페이지 탐색에 사용합니다.
|
|
551
|
+
|
|
552
|
+
### Zoom Control · Tools
|
|
553
|
+
|
|
554
|
+
`db-zoom-control` / React `ZoomControl` combines a percentage menu, zoom in/out, fit and fullscreen. It composes the official DesignToolbar and ToolButton. Set `target-id`, `content-width` and `content-height` to calculate fit. The consumer applies `db-change.detail.value / 100` to its content transform; content dimensions are the **untransformed** dimensions. `value` is a percentage (default 100), bounded by `min=10` and `max=400`; `step=25` controls button increments. `fit-padding=24` reserves space at each edge. In fit mode a ResizeObserver keeps the scale in sync with the target size. An external value change exits fit mode. Without content dimensions, handle `db-fit` to compute your own fit.
|
|
555
|
+
|
|
556
|
+
The target (parent by default) needs `tabindex="0"` to receive shortcuts: `+`/`−` zoom, `Shift+0` fit, `Shift+1` 100%, `Shift+F` fullscreen. Shortcuts only run while focus is in that target or the control. Text fields, editable content, composing input, menus and Ctrl/Cmd/Alt combinations are excluded. When controls share a target, the first enabled control containing the action owns that shortcut. `shortcuts="false"` (React `shortcuts={false}`) opts out. Arrow/Home/End navigation and menu focus are inherited from DesignToolbar/ToolButton.
|
|
557
|
+
|
|
558
|
+
`disabled`, `hide-fullscreen`, `size="xs|s|m"`, `tone="default|dark"` are available. Fullscreen uses the standard browser API, follows Escape exits, disables when unavailable and emits `db-error` on rejection. `db-fullscreen-change` reports target state. React maps these to `onChange`, `onFit`, `onFullscreenChange`, `onError`. Programmatic value changes do not emit `db-change`. Korean/English labels use the existing `dbUseLocale` / `dbSetLocale` API; consumers do not need to copy translation tables.
|
|
559
|
+
|
|
560
|
+
Choose a smaller `size="xs"` (24px buttons), `s` (28px), or `m` (32px). `controls` chooses actions in canonical order: `['out','value','in','fit','fullscreen']`. Examples: `controls={['out','value','in']}` excludes Fit/fullscreen; `zoomStyle="plain"` switches the combined toolbar to simple minus/plus icons while retaining percentage, Fit and fullscreen; `controls={['fit','fullscreen']}` omits zoom completely. For HTML, serialize `controls='["out","value","in","fit","fullscreen"]'` and `zoom-style="plain"`. Hidden actions do not respond to shortcuts; Fit is also removed from the percentage menu when omitted. Split controls sharing one target delegate each shortcut to the first enabled instance containing that action.
|
|
561
|
+
|
|
562
|
+
### Input Group · joined fields
|
|
563
|
+
|
|
564
|
+
Use `InputGroup` / `db-input-group` for a field with leading/trailing Button or Select. Keep one consumer-owned `<div slot="control">` containing one unlabelled Input and its companion controls. This wrapper stays in place across renders; native values, selection, input events and form participation remain with the child components.
|
|
565
|
+
|
|
566
|
+
```tsx
|
|
567
|
+
<InputGroup label="Website" helperText={protocol + domain} size="m">
|
|
568
|
+
<div slot="control">
|
|
569
|
+
<Select aria-label="Protocol" options={protocols} value={protocol} onChange={e => setProtocol(String(e.detail.value))} />
|
|
570
|
+
<Input name="domain" value={domain} onInput={e => setDomain(e.detail.value)} />
|
|
571
|
+
<Button variant="tertiary" onPress={save}>Apply</Button>
|
|
572
|
+
</div>
|
|
573
|
+
</InputGroup>
|
|
574
|
+
```
|
|
575
|
+
|
|
576
|
+
`size="s|m|l"` sets a consistent field height. `disabled` disables all three control kinds and preserves individually disabled children when re-enabled. `label` focuses/names the main Input. `required`/`error` apply to that Input; `helper-text`/`error-message` render below the combined field and are linked by `aria-describedby`. Give Selects and icon-only buttons their own accessible labels. Child labels/helper text belong outside the shared control row; use group-level labels/messages. Buttons keep independent focus, Select keeps its listbox keyboard behavior. Event handlers are attached to the individual child; the group does not synthesize a combined value.
|
|
577
|
+
|
|
578
|
+
Icon actions use the official `Button iconOnly` inside `Tooltip focus-target="child"` on either side of the Input. The group sizes tooltip-wrapped and direct icon buttons consistently at s/m/l, preserves the outer corners and propagates disabled to the actual button. Give each button an `aria-label` and localize its tooltip.
|
|
579
|
+
|
|
580
|
+
```tsx
|
|
581
|
+
<InputGroup label="Search">
|
|
582
|
+
<div slot="control">
|
|
583
|
+
<Input value={query} onInput={e => setQuery(e.detail.value)} />
|
|
584
|
+
<Tooltip content="Search projects" focus-target="child">
|
|
585
|
+
<Button iconOnly variant="primary" aria-label="Search projects" onPress={search}>
|
|
586
|
+
<SearchIcon size={16} aria-hidden />
|
|
587
|
+
</Button>
|
|
588
|
+
</Tooltip>
|
|
589
|
+
</div>
|
|
590
|
+
</InputGroup>
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
The Input Group example includes a trailing search action, a leading reference-ID action and paired reset/copy actions. Copy handles clipboard rejection with a visible message.
|
|
594
|
+
|
|
595
|
+
### Tool Panel header actions
|
|
596
|
+
|
|
597
|
+
Place one container in `slot="actions"` to add toggles, buttons or menus to the right of `heading`. It can coexist with `closable`. Actions remain available when a collapsible panel is collapsed, and clicking an action does not toggle the panel. Connect child handlers/state yourself; the slot does not own business state.
|
|
598
|
+
|
|
599
|
+
```tsx
|
|
600
|
+
<ToolPanel heading="Fill" collapsible appearance="card" closable>
|
|
601
|
+
<div slot="actions">
|
|
602
|
+
<Toggle size="s" aria-label="Enable fill" checked={enabled} onChange={e => setEnabled(e.detail.checked)} />
|
|
603
|
+
<Button size="s" variant="ghost" onPress={reset}>Reset</Button>
|
|
604
|
+
</div>
|
|
605
|
+
<div slot="body"><Input label="Color" disabled={!enabled} value={color} onInput={e => setColor(e.detail.value)} /></div>
|
|
606
|
+
</ToolPanel>
|
|
607
|
+
```
|
|
608
|
+
|
|
@@ -14,10 +14,12 @@ interface DbAchievementItem {
|
|
|
14
14
|
/** Presentation only. The owner supplies server-validated awards/progress; selection grants nothing. */
|
|
15
15
|
declare class DbAchievementList extends DbElement {
|
|
16
16
|
private data;
|
|
17
|
+
private unsubscribeLocale?;
|
|
17
18
|
static get observedAttributes(): string[];
|
|
18
19
|
get items(): DbAchievementItem[];
|
|
19
20
|
set items(value: DbAchievementItem[]);
|
|
20
21
|
connectedCallback(): void;
|
|
22
|
+
disconnectedCallback(): void;
|
|
21
23
|
attributeChangedCallback(): void;
|
|
22
24
|
private render;
|
|
23
25
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{dbText as t}from"../core/i18n.js";import{DbElement as
|
|
1
|
+
import{subscribeDbLocale as e,dbText as t}from"../core/i18n.js";import{DbElement as i}from"../core/base-element.js";import{textNode as s,replaceView as a}from"../core/credit-ui.js";import{createDbIcon as r}from"./db-icons.js";import"./db-button.js";function achievementItems(e){const t=new Set;return Array.isArray(e)?e.filter(e=>!(!e||"string"!=typeof e.id||!e.id||t.has(e.id)||"string"!=typeof e.title||"string"!=typeof e.description||!["locked","earned","needs_review"].includes(e.state))&&(t.add(e.id),!0)).map(e=>({id:e.id,title:e.title,description:e.description,state:e.state,current:Number.isSafeInteger(e.current)&&e.current>=0?e.current:void 0,target:Number.isSafeInteger(e.target)&&e.target>0?e.target:void 0,earnedLabel:"string"==typeof e.earnedLabel?e.earnedLabel:void 0,motif:["star","award","bulb","heart"].includes(e.motif||"")?e.motif:"award"})):[]}function achievementMedallion(e){const t=s("div","designbase-wc-achievement-art","");return t.dataset.state=e.state,t.setAttribute("aria-hidden","true"),t.append(r(e.motif||"award",40)),t}class DbAchievementList extends i{constructor(){super(...arguments),this.data=[]}static get observedAttributes(){return["label","empty-label","loading","error"]}get items(){return this.data.map(e=>({...e}))}set items(e){this.data=achievementItems(e),this.isConnected&&this.render()}connectedCallback(){this.upgradeProperties(["items"]),this.unsubscribeLocale?.(),this.unsubscribeLocale=e(()=>this.render()),this.cleanups.listen(this,"db-click",e=>{const t=e.target instanceof HTMLElement?e.target.closest("[data-badge-id]"):null;t&&this.contains(t)&&!this.hasAttribute("loading")&&!this.hasAttribute("error")&&this.dbEmit("select",{id:t.dataset.badgeId})}),this.render()}disconnectedCallback(){this.unsubscribeLocale?.(),this.unsubscribeLocale=void 0,super.disconnectedCallback()}attributeChangedCallback(){this.isConnected&&this.render()}render(){this.classList.add("designbase-wc-achievement-list"),this.setAttribute("aria-busy",String(this.hasAttribute("loading")));const e=s("div","designbase-wc-achievement-list__grid","");e.setAttribute("role","list"),e.setAttribute("aria-label",this.getAttribute("label")||t("뱃지 목록"));const i=this.getAttribute("error")||(this.hasAttribute("loading")?t("뱃지를 불러오고 있어요"):this.data.length?"":this.getAttribute("empty-label")||t("아직 등록된 뱃지가 없어요"));if(i){const e=s("p","designbase-wc-achievement-list__message",i);return e.setAttribute("role",this.hasAttribute("error")?"alert":"status"),void a(this,e)}for(const i of this.data){const a=s("article","designbase-wc-achievement-list__card","");if(a.dataset.state=i.state,a.setAttribute("role","listitem"),a.append(achievementMedallion(i),s("span","designbase-wc-achievement-list__state",t("earned"===i.state?"획득 완료":"needs_review"===i.state?"기록 확인 필요":"도전 중")),s("h3","",i.title),s("p","designbase-wc-achievement-list__description",i.description)),"locked"===i.state&&i.target){const e=document.createElement("progress");e.max=i.target,e.value=Math.min(i.current||0,i.target),e.setAttribute("aria-label",i.title+" "+t("진행률")),a.append(e,s("span","designbase-wc-achievement-list__date",`${Math.min(i.current||0,i.target)} / ${i.target}`))}else a.append(s("span","designbase-wc-achievement-list__date","earned"===i.state?i.earnedLabel||t("나의 컬렉션에 담았어요"):t("needs_review"===i.state?"연결된 기록을 다시 확인해 주세요":"다음 순간을 기다려요")));const r=document.createElement("db-button");r.setAttribute("variant","secondary"),r.setAttribute("size","s"),r.dataset.badgeId=i.id,r.dataset.action=i.id,r.textContent=t("자세히 보기"),r.setAttribute("aria-label",i.title+" "+t("자세히 보기")),a.append(r),e.append(a)}a(this,e)}}customElements.get("db-achievement-list")||customElements.define("db-achievement-list",DbAchievementList);export{DbAchievementList,achievementItems,achievementMedallion};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{
|
|
1
|
+
import{subscribeDbLocale as e,dbText as t,getDbLocale as a}from"../core/i18n.js";import{sanitizeHref as s}from"../core/safe-url.js";import{ACTIVITY_HEATMAP_SIZES as n,ACTIVITY_HEATMAP_COLOR_SCHEMES as r}from"../node_modules/@designbasekorea/ui-contracts/dist/components.js";const l="designbase-wc-activity-heatmap";class DbActivityHeatmap extends HTMLElement{constructor(){super(...arguments),this.handleClick=e=>{const t=e.target,a=t?.closest(`.${l}__cell`);if(!a||a.disabled)return;const s=a.dataset.date;if(!s)return;const n=Number(a.dataset.value??"0"),r=Number(a.dataset.level??"0");this.dispatchEvent(new CustomEvent("db-cell-click",{detail:{date:s,value:n,level:r},bubbles:!0,composed:!0}))}}static get observedAttributes(){return["data","end-date","weeks","week-starts-on","size","locale","color-scheme","show-legend","show-month-labels","show-day-labels","legend-less-label","legend-more-label","value-label","max-value","footer-text","footer-href"]}connectedCallback(){this.addEventListener("click",this.handleClick),this.unsubscribeLocale?.(),this.unsubscribeLocale=e(()=>{const e=this.contains(document.activeElement)?document.activeElement.dataset.date:void 0;this.render(),e&&Array.from(this.querySelectorAll(`.${l}__cell`)).find(t=>t.dataset.date===e)?.focus()}),this.render()}disconnectedCallback(){this.unsubscribeLocale?.(),this.unsubscribeLocale=void 0,this.removeEventListener("click",this.handleClick)}attributeChangedCallback(){this.render()}parseData(){const e=this.getAttribute("data");if(!e)return[];try{const t=JSON.parse(e);return Array.isArray(t)?t.filter(e=>e&&"string"==typeof e.date&&"number"==typeof e.value):[]}catch{return[]}}parseEndDate(){const e=this.getAttribute("end-date");if(e){const t=this.parseISODate(e);if(t)return t}const t=new Date;return new Date(t.getFullYear(),t.getMonth(),t.getDate())}parseISODate(e){const t=/^(\d{4})-(\d{2})-(\d{2})/.exec(e.trim());if(!t)return null;const a=Number(t[1]),s=Number(t[2]),n=Number(t[3]);return a&&s&&n?new Date(a,s-1,n):null}formatISODate(e){return`${e.getFullYear()}-${String(e.getMonth()+1).padStart(2,"0")}-${String(e.getDate()).padStart(2,"0")}`}addDays(e,t){const a=new Date(e);return a.setDate(a.getDate()+t),a}startOfWeek(e,t){const a=(e.getDay()-t+7)%7;return this.addDays(e,-a)}isAfter(e,t){return e.getTime()>t.getTime()}buildWeeks(e,t,a){const s=this.startOfWeek(e,a),n=this.addDays(s,7*-(t-1)),r=[];for(let e=0;e<t;e+=1){const t=[];for(let a=0;a<7;a+=1)t.push(this.addDays(n,7*e+a));r.push(t)}return r}resolveLevel(e,t,a){if(e<=0||t<=0)return 0;const s=e/t;return Math.min(a,Math.max(1,Math.ceil(s*a)))}formatMonthLabel(e,t){return new Intl.DateTimeFormat(t,{month:"short"}).format(e)}formatTooltip(e,s,n,r){const l=new Intl.DateTimeFormat(n,{year:"numeric",month:"long",day:"numeric"}).format(e);if(s<=0)return`${l}: ${t("활동 없음")}`;const i=this.hasAttribute("value-label")||"en"!==a()||1!==s?r:" time";return`${l}: ${s.toLocaleString(n)}${i}`}getDayLabels(e,t){const a=Array.from({length:7},(a,s)=>{const n=new Date(2024,0,7+(e+s)%7);return new Intl.DateTimeFormat(t,{weekday:"short"}).format(n)}),s=1===e?[0,2,4,6]:[1,3,5,0];return a.map((e,t)=>s.includes(t)?e:"")}getMonthLabels(e,t){const a=e.map(e=>{const t=e.find(e=>e.getDate()<=7)??e[0];return t.getMonth()+12*t.getFullYear()}),s=e.map(()=>"");let n=0;for(;n<e.length;){const r=a[n];let l=n+1;for(;l<e.length&&a[l]===r;)l+=1;if(l-n>=2){const a=e[n],r=a.find(e=>e.getDate()<=7)??a[0];s[n]=this.formatMonthLabel(r,t)}n=l}return s}render(){const e=this.parseData(),i=new Map(e.map(e=>[e.date,e.value])),o=this.parseEndDate(),c=Math.max(1,Number(this.getAttribute("weeks")??"53")||53),d="0"===this.getAttribute("week-starts-on")?0:1,m=this.getAttribute("size")||"m",u=n.includes(m)?m:"m",h=this.getAttribute("locale")||("ko"===a()?"ko-KR":"en-US"),b=this.getAttribute("color-scheme")||"green",p=r.includes(b)?b:"green",f="false"!==this.getAttribute("show-legend"),g="false"!==this.getAttribute("show-month-labels"),_="false"!==this.getAttribute("show-day-labels"),v=this.getAttribute("legend-less-label")||t("적음"),$=this.getAttribute("legend-more-label")||t("많음"),E=this.getAttribute("value-label")||`${"en"===a()?" ":""}${t("회")}`,A=this.getAttribute("footer-text")||"",y=this.getAttribute("footer-href")||"",D=this.getAttribute("max-value"),N=e.map(e=>e.value).filter(e=>e>0),k=N.length?Math.max(...N):0,w=D?Math.max(Number(D)||0,k):k,x=this.buildWeeks(o,c,d),L=this.getMonthLabels(x,h),C=this.getDayLabels(d,h);this.className=[l,`${l}--size-${u}`,`${l}--color-${p}`,f&&`${l}--with-legend`].filter(Boolean).join(" "),this.replaceChildren();const M=document.createElement("div");M.className=`${l}__viewport`;const S=document.createElement("div");if(S.className=`${l}__scroll-inner`,_){const e=document.createElement("div");e.className=`${l}__sticky-rail`;const t=document.createElement("div");t.className=`${l}__sticky-spacer`,g||t.classList.add(`${l}__sticky-spacer--hidden`);const a=document.createElement("div");a.className=`${l}__days`,a.setAttribute("aria-hidden","true"),C.forEach(e=>{const t=document.createElement("span");t.className=`${l}__day`,t.textContent=e,a.append(t)}),e.append(t,a),S.append(e)}const T=document.createElement("div");if(T.className=`${l}__grid-area`,g){const e=document.createElement("div");e.className=`${l}__months`,e.setAttribute("aria-hidden","true"),L.forEach(t=>{const a=document.createElement("span");a.className=`${l}__month`,a.textContent=t,e.append(a)}),T.append(e)}const I=document.createElement("div");if(I.className=`${l}__weeks`,I.setAttribute("role","grid"),I.setAttribute("aria-label",t("활동 히트맵")),x.forEach(e=>{const t=document.createElement("div");t.className=`${l}__week`,t.setAttribute("role","row"),e.forEach(e=>{const a=this.formatISODate(e),s=this.isAfter(e,o),n=s?0:i.get(a)??0,r=s?0:this.resolveLevel(n,w,4),c=document.createElement("button");c.type="button",c.className=[`${l}__cell`,`${l}__cell--level-${r}`,s&&`${l}__cell--future`].filter(Boolean).join(" "),c.dataset.date=a,c.dataset.value=String(n),c.dataset.level=String(r),c.title=this.formatTooltip(e,n,h,E),c.setAttribute("aria-label",this.formatTooltip(e,n,h,E)),c.disabled=s,t.append(c)}),I.append(t)}),T.append(I),S.append(T),M.append(S),this.append(M),f||A){const e=document.createElement("div");if(e.className=`${l}__footer`,A)if(y){const t=document.createElement("a");t.className=`${l}__footer-link`,t.href=s(y,"#"),t.textContent=A,e.append(t)}else{const t=document.createElement("span");t.className=`${l}__footer-text`,t.textContent=A,e.append(t)}else e.append(document.createElement("span"));if(f){const t=document.createElement("div");t.className=`${l}__legend`,t.setAttribute("aria-hidden","true");const a=document.createElement("span");a.className=`${l}__legend-label`,a.textContent=v;const s=document.createElement("div");s.className=`${l}__legend-scale`;for(let e=0;e<=4;e+=1){const t=document.createElement("span");t.className=`${l}__legend-cell ${l}__legend-cell--level-${e}`,s.append(t)}const n=document.createElement("span");n.className=`${l}__legend-label`,n.textContent=$,t.append(a,s,n),e.append(t)}this.append(e)}}}customElements.get("db-activity-heatmap")||customElements.define("db-activity-heatmap",DbActivityHeatmap);export{DbActivityHeatmap};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
declare class DbAdBanner extends HTMLElement {
|
|
2
2
|
static get observedAttributes(): string[];
|
|
3
3
|
private built;
|
|
4
|
+
private unsubscribeLocale?;
|
|
4
5
|
private visible;
|
|
5
6
|
private progress;
|
|
6
7
|
private autoCloseTimer?;
|
|
@@ -55,6 +56,7 @@ declare class DbAdBanner extends HTMLElement {
|
|
|
55
56
|
private syncLink;
|
|
56
57
|
/** show-countdown + end-date 가 있을 때만 카운트다운을 마운트한다. */
|
|
57
58
|
private syncCountdown;
|
|
59
|
+
private syncLocale;
|
|
58
60
|
private updateUI;
|
|
59
61
|
}
|
|
60
62
|
declare global {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{dbText as t}from"../core/i18n.js";import{createDbIcon as e}from"./db-icons.js";import{sanitizeHref as i}from"../core/safe-url.js";import{coerceToContract as s}from"../node_modules/@designbasekorea/ui-contracts/dist/primitives.js";import{AD_BANNER_VARIANTS as n,AD_BANNER_SIZES as r}from"../node_modules/@designbasekorea/ui-contracts/dist/components.js";const a="designbase-wc-ad-banner",l={hero:"지금 바로 시작하세요",topbar:"🎉 신규 가입 시 10,000원 적립금 증정!",card:"프리미엄 멤버십",floating:"오늘만 특가!"},o={hero:"최대 50% 할인된 가격으로 프리미엄 서비스를 경험해보세요. 기간 한정 특별 혜택!",topbar:"오늘 하루만 특별 혜택",card:"무제한 서비스 이용권을 특별가에 만나보세요",floating:"장바구니에 담긴 상품 추가 20% 할인"},h={hero:"자세히 보기",topbar:"지금 가입하기",card:"알아보기",floating:"할인받기"},d="star-filled",c={primary:"primary",secondary:"secondary",success:"success",warning:"warning",error:"error"};class DbAdBanner extends HTMLElement{constructor(){super(...arguments),this.built=!1,this.visible=!0,this.progress=100}static get observedAttributes(){return["type","variant","size","title","subtitle","cta-text","image","image-alt","href","target","auto-close","close-delay","background","animation-type","grid-type","use-random-gradient","gradient-scheme","gradient-tone","gradient-animated","countdown-end-date","show-countdown","open"]}connectedCallback(){this.style.display="block",this.built||(this.built=!0,this.build()),this.updateUI()}disconnectedCallback(){this.stopAutoClose()}attributeChangedCallback(t){this.built&&("open"===t&&(this.visible="false"!==this.getAttribute("open")),this.updateUI())}getBool(t,e=!1){if(!this.hasAttribute(t))return e;const i=this.getAttribute(t);return""===i||"true"===i}getType(){const t=this.getAttribute("type")||"hero";return["hero","topbar","card","floating"].includes(t)?t:"hero"}getVariant(){return s(this.getAttribute("variant"),n,"primary")}getSize(){return s(this.getAttribute("size"),r,"m")}ctaButtonSize(t,e){return"card"===t?"s":"floating"===t?"s"===e?"s":"m":e}build(){this.rootEl=document.createElement("div"),this.contentEl=document.createElement("div"),this.contentEl.className=`${a}__content`,this.closeBtn=document.createElement("button"),this.closeBtn.type="button",this.closeBtn.className=`${a}__close`,this.closeBtn.setAttribute("aria-label",t("닫기")),this.closeBtn.append(e("close",16)),this.closeBtn.addEventListener("click",t=>{t.stopPropagation(),this.handleClose()}),this.imageEl=document.createElement("img"),this.imageEl.className=`${a}__image`,this.imageEl.loading="lazy";const i=document.createElement("div");i.className=`${a}__main`,this.mainEl=i,this.iconEl=document.createElement("div"),this.iconEl.className=`${a}__icon`,this.labelEl=document.createElement("span"),this.labelEl.className=`${a}__label`,this.labelEl.textContent=t("특별 이벤트"),this.badgeEl=document.createElement("span"),this.badgeEl.className=`${a}__badge`,this.badgeEl.textContent="HOT DEAL",this.titleEl=document.createElement("h2"),this.titleEl.className=`${a}__title`,this.subtitleEl=document.createElement("p"),this.subtitleEl.className=`${a}__subtitle`;const s=document.createElement("div");s.className=`${a}__text`,this.textEl=s,this.countdownWrapEl=document.createElement("div"),this.countdownWrapEl.className=`${a}__countdown`,s.append(this.titleEl,this.subtitleEl),this.actionsEl=document.createElement("div"),this.actionsEl.className=`${a}__actions`,this.ctaEl=document.createElement("db-button"),this.ctaEl.className=`${a}__cta`,this.ctaEl.setAttribute("variant","primary"),this.ctaEl.setAttribute("end-icon","arrow-right"),this.ctaEl.addEventListener("click",t=>{t.stopPropagation(),this.dispatchEvent(new CustomEvent("db-cta-click",{bubbles:!0,composed:!0}))}),this.actionsEl.append(this.ctaEl),i.append(s,this.actionsEl),this.progressEl=document.createElement("div"),this.progressEl.className=`${a}__progress`,this.progressBarEl=document.createElement("div"),this.progressBarEl.className=`${a}__progress-bar`,this.progressEl.append(this.progressBarEl),this.contentEl.append(this.closeBtn,i,this.progressEl),this.rootEl.append(this.contentEl),this.append(this.rootEl),this.contentEl.addEventListener("click",t=>{"topbar"===this.getType()&&(t.target?.closest(`.${a}__close`)||this.dispatchEvent(new CustomEvent("db-cta-click",{bubbles:!0,composed:!0})))})}handleClose(){this.visible=!1,this.style.display="none",this.setAttribute("open","false"),this.stopAutoClose(),this.dispatchEvent(new CustomEvent("db-close",{bubbles:!0,composed:!0}))}stopAutoClose(){this.autoCloseTimer&&(window.clearInterval(this.autoCloseTimer),this.autoCloseTimer=void 0)}startAutoClose(t){this.stopAutoClose(),this.progress=100,this.autoCloseTimer=window.setInterval(()=>{this.progress-=100/(t/100),this.progressBarEl&&(this.progressBarEl.style.width=`${Math.max(this.progress,0)}%`),this.progress<=0&&this.handleClose()},100)}getBackground(){const t=this.getAttribute("background");return t&&["solid","gradient","animation","grid"].includes(t)?t:this.getBool("use-random-gradient")?"gradient":"solid"}ensureAnimBg(){if(!this.animBgEl){const t=document.createElement("div");t.className=`${a}__backdrop`;const e=document.createElement("db-animation-background");e.className=`${a}__anim-bg`,e.setAttribute("width","100%"),e.setAttribute("height","100%"),t.append(e),this.animBgEl=t,this.animBgInner=e}return this.animBgEl}ensureGridBg(){if(!this.gridBgEl){const t=document.createElement("div");t.className=`${a}__backdrop`;const e=document.createElement("db-grid-background");e.className=`${a}__grid-bg`,e.setAttribute("width","100%"),e.setAttribute("height","100%"),t.append(e),this.gridBgEl=t,this.gridBgInner=e}return this.gridBgEl}clearBackdrop(t){"gradient"!==t&&this.gradientEl&&(this.gradientEl.remove(),this.gradientEl=void 0),"animation"!==t&&this.animBgEl&&(this.animBgEl.remove(),this.animBgEl=void 0,this.animBgInner=void 0),"grid"!==t&&this.gridBgEl&&(this.gridBgEl.remove(),this.gridBgEl=void 0,this.gridBgInner=void 0)}renderBackground(t,e){if(!this.rootEl||!this.contentEl)return;if(this.clearBackdrop(t),"gradient"===t){this.gradientEl||(this.gradientEl=document.createElement("db-random-gradient"),this.gradientEl.className=`${a}__gradient-bg`,this.gradientEl.setAttribute("height","100%"),this.gradientEl.setAttribute("width","100%"));const t=this.getAttribute("gradient-tone")||"vivid",i=!this.hasAttribute("gradient-animated")||"false"!==this.getAttribute("gradient-animated");return this.gradientEl.setAttribute("scheme",this.getAttribute("gradient-scheme")||c[e]),this.gradientEl.setAttribute("tone",t),this.gradientEl.setAttribute("animated",i?"true":"false"),this.gradientEl.parentElement!==this.rootEl&&this.rootEl.replaceChildren(this.gradientEl),void(this.contentEl.parentElement!==this.gradientEl&&this.gradientEl.append(this.contentEl))}const i=[];if("animation"===t){const t=this.ensureAnimBg();this.animBgInner?.setAttribute("type",this.getAttribute("animation-type")||"graphite"),this.animBgInner?.setAttribute("theme","dark"),i.push(t)}else if("grid"===t){const t=this.ensureGridBg();this.gridBgInner?.setAttribute("type",this.getAttribute("grid-type")||"lines"),this.gridBgInner?.setAttribute("color","var(--db-text-inverse-primary)"),this.gridBgInner?.setAttribute("opacity","0.18"),this.gridBgInner?.setAttribute("fade","vignette"),this.gridBgInner?.setAttribute("animated",""),i.push(t)}const s=[...i,this.contentEl],n=Array.from(this.rootEl.children);n.length===s.length&&s.every((t,e)=>n[e]===t)||this.rootEl.replaceChildren(...s)}syncIcon(t){this.mainEl&&this.textEl&&this.iconEl&&("card"===t?(this.iconEl.replaceChildren(e(d,32)),this.iconEl.parentElement!==this.mainEl&&this.mainEl.insertBefore(this.iconEl,this.textEl)):this.iconEl.remove())}syncBadge(t){this.textEl&&this.badgeEl&&("floating"===t?this.badgeEl.parentElement!==this.textEl&&this.textEl.insertBefore(this.badgeEl,this.textEl.firstChild):this.badgeEl.remove())}syncImage(t,e){this.contentEl&&this.imageEl&&this.mainEl&&("floating"===t&&e?(this.imageEl.src=e,this.imageEl.alt=this.getAttribute("image-alt")||"",this.imageEl.parentElement!==this.contentEl&&this.contentEl.insertBefore(this.imageEl,this.mainEl)):this.imageEl.remove())}syncLink(t,e){if(!this.contentEl)return;const s=i(e,"");if("topbar"!==t||!s)return void this.linkEl?.remove();this.linkEl||(this.linkEl=document.createElement("a"),this.linkEl.className=`${a}__link`),this.linkEl.setAttribute("href",s);const n=this.getAttribute("target");n?this.linkEl.setAttribute("target",n):this.linkEl.removeAttribute("target"),this.linkEl.setAttribute("aria-label",this.titleEl?.textContent||"배너 링크"),this.linkEl.parentElement!==this.contentEl&&this.contentEl.append(this.linkEl)}syncCountdown(t,e){this.textEl&&this.countdownWrapEl&&(t&&e?(this.countdownEl||(this.countdownEl=document.createElement("db-countdown"),this.countdownEl.setAttribute("size","s"),this.countdownEl.setAttribute("variant","compact"),this.countdownEl.setAttribute("show-labels","false"),this.countdownWrapEl.replaceChildren(this.countdownEl)),this.countdownEl.setAttribute("end-date",e),this.countdownWrapEl.parentElement!==this.textEl&&this.textEl.append(this.countdownWrapEl)):this.countdownWrapEl.remove())}updateUI(){if(!this.visible||"false"===this.getAttribute("open"))return void(this.style.display="none");this.style.display="block";const t=this.getType(),e=this.getVariant(),i=this.getSize(),s=this.getAttribute("title")||l[t],n=this.getAttribute("subtitle")||o[t],r=this.getAttribute("cta-text")||h[t],d=this.getBool("auto-close"),c=Math.max(Number(this.getAttribute("close-delay")||5e3),1e3),g=this.getBackground(),E=this.getAttribute("gradient-tone")||"vivid",u=this.getBool("show-countdown"),m=this.getAttribute("countdown-end-date");this.rootEl&&(this.rootEl.className=[a,`${a}--type-${t}`,`${a}--variant-${e}`,`${a}--size-${i}`,`${a}--bg-${g}`,d&&`${a}--auto-close`,"gradient"===g&&`${a}--with-gradient`,"gradient"===g&&"light"===E&&`${a}--gradient-light`].filter(Boolean).join(" ")),this.renderBackground(g,e),this.syncIcon(t),this.syncBadge(t),this.syncImage(t,this.getAttribute("image")),this.syncLink(t,this.getAttribute("href")),this.titleEl&&(this.titleEl.textContent=s),this.subtitleEl&&(this.subtitleEl.textContent=n),this.syncCountdown(u,m),this.actionsEl&&(this.actionsEl.hidden="topbar"===t),this.ctaEl&&(this.ctaEl.textContent=r,this.ctaEl.setAttribute("size",this.ctaButtonSize(t,i)),"floating"===t?this.ctaEl.setAttribute("full-width",""):this.ctaEl.removeAttribute("full-width")),this.progressEl&&(this.progressEl.hidden=!d),this.progressBarEl&&d&&(this.progressBarEl.style.width=`${this.progress}%`),this.stopAutoClose(),d&&this.startAutoClose(c)}}customElements.get("db-ad-banner")||customElements.define("db-ad-banner",DbAdBanner);export{DbAdBanner};
|
|
1
|
+
import{subscribeDbLocale as t,dbText as e}from"../core/i18n.js";import{createDbIcon as i}from"./db-icons.js";import{sanitizeHref as s}from"../core/safe-url.js";import{coerceToContract as n}from"../node_modules/@designbasekorea/ui-contracts/dist/primitives.js";import{AD_BANNER_VARIANTS as r,AD_BANNER_SIZES as l}from"../node_modules/@designbasekorea/ui-contracts/dist/components.js";const a="designbase-wc-ad-banner",o={hero:"지금 바로 시작하세요",topbar:"🎉 신규 가입 시 10,000원 적립금 증정!",card:"프리미엄 멤버십",floating:"오늘만 특가!"},h={hero:"최대 50% 할인된 가격으로 프리미엄 서비스를 경험해보세요. 기간 한정 특별 혜택!",topbar:"오늘 하루만 특별 혜택",card:"무제한 서비스 이용권을 특별가에 만나보세요",floating:"장바구니에 담긴 상품 추가 20% 할인"},c={hero:"자세히 보기",topbar:"지금 가입하기",card:"알아보기",floating:"할인받기"},d="star-filled",g={primary:"primary",secondary:"secondary",success:"success",warning:"warning",error:"error"};class DbAdBanner extends HTMLElement{constructor(){super(...arguments),this.built=!1,this.visible=!0,this.progress=100}static get observedAttributes(){return["type","variant","size","title","subtitle","cta-text","image","image-alt","href","target","auto-close","close-delay","background","animation-type","grid-type","use-random-gradient","gradient-scheme","gradient-tone","gradient-animated","countdown-end-date","show-countdown","open"]}connectedCallback(){this.style.display="block",this.built||(this.built=!0,this.build()),this.updateUI(),this.unsubscribeLocale?.(),this.unsubscribeLocale=t(()=>this.syncLocale())}disconnectedCallback(){this.unsubscribeLocale?.(),this.unsubscribeLocale=void 0,this.stopAutoClose()}attributeChangedCallback(t){this.built&&("open"===t&&(this.visible="false"!==this.getAttribute("open")),this.updateUI())}getBool(t,e=!1){if(!this.hasAttribute(t))return e;const i=this.getAttribute(t);return""===i||"true"===i}getType(){const t=this.getAttribute("type")||"hero";return["hero","topbar","card","floating"].includes(t)?t:"hero"}getVariant(){return n(this.getAttribute("variant"),r,"primary")}getSize(){return n(this.getAttribute("size"),l,"m")}ctaButtonSize(t,e){return"card"===t?"s":"floating"===t?"s"===e?"s":"m":e}build(){this.rootEl=document.createElement("div"),this.contentEl=document.createElement("div"),this.contentEl.className=`${a}__content`,this.closeBtn=document.createElement("button"),this.closeBtn.type="button",this.closeBtn.className=`${a}__close`,this.closeBtn.setAttribute("aria-label",e("닫기")),this.closeBtn.append(i("close",16)),this.closeBtn.addEventListener("click",t=>{t.stopPropagation(),this.handleClose()}),this.imageEl=document.createElement("img"),this.imageEl.className=`${a}__image`,this.imageEl.loading="lazy";const t=document.createElement("div");t.className=`${a}__main`,this.mainEl=t,this.iconEl=document.createElement("div"),this.iconEl.className=`${a}__icon`,this.labelEl=document.createElement("span"),this.labelEl.className=`${a}__label`,this.labelEl.textContent=e("특별 이벤트"),this.badgeEl=document.createElement("span"),this.badgeEl.className=`${a}__badge`,this.badgeEl.textContent="HOT DEAL",this.titleEl=document.createElement("h2"),this.titleEl.className=`${a}__title`,this.subtitleEl=document.createElement("p"),this.subtitleEl.className=`${a}__subtitle`;const s=document.createElement("div");s.className=`${a}__text`,this.textEl=s,this.countdownWrapEl=document.createElement("div"),this.countdownWrapEl.className=`${a}__countdown`,s.append(this.titleEl,this.subtitleEl),this.actionsEl=document.createElement("div"),this.actionsEl.className=`${a}__actions`,this.ctaEl=document.createElement("db-button"),this.ctaEl.className=`${a}__cta`,this.ctaEl.setAttribute("variant","primary"),this.ctaEl.setAttribute("end-icon","arrow-right"),this.ctaEl.addEventListener("click",t=>{t.stopPropagation(),this.dispatchEvent(new CustomEvent("db-cta-click",{bubbles:!0,composed:!0}))}),this.actionsEl.append(this.ctaEl),t.append(s,this.actionsEl),this.progressEl=document.createElement("div"),this.progressEl.className=`${a}__progress`,this.progressBarEl=document.createElement("div"),this.progressBarEl.className=`${a}__progress-bar`,this.progressEl.append(this.progressBarEl),this.contentEl.append(this.closeBtn,t,this.progressEl),this.rootEl.append(this.contentEl),this.append(this.rootEl),this.contentEl.addEventListener("click",t=>{"topbar"===this.getType()&&(t.target?.closest(`.${a}__close`)||this.dispatchEvent(new CustomEvent("db-cta-click",{bubbles:!0,composed:!0})))})}handleClose(){this.visible=!1,this.style.display="none",this.setAttribute("open","false"),this.stopAutoClose(),this.dispatchEvent(new CustomEvent("db-close",{bubbles:!0,composed:!0}))}stopAutoClose(){this.autoCloseTimer&&(window.clearInterval(this.autoCloseTimer),this.autoCloseTimer=void 0)}startAutoClose(t){this.stopAutoClose(),this.progress=100,this.autoCloseTimer=window.setInterval(()=>{this.progress-=100/(t/100),this.progressBarEl&&(this.progressBarEl.style.width=`${Math.max(this.progress,0)}%`),this.progress<=0&&this.handleClose()},100)}getBackground(){const t=this.getAttribute("background");return t&&["solid","gradient","animation","grid"].includes(t)?t:this.getBool("use-random-gradient")?"gradient":"solid"}ensureAnimBg(){if(!this.animBgEl){const t=document.createElement("div");t.className=`${a}__backdrop`;const e=document.createElement("db-animation-background");e.className=`${a}__anim-bg`,e.setAttribute("width","100%"),e.setAttribute("height","100%"),t.append(e),this.animBgEl=t,this.animBgInner=e}return this.animBgEl}ensureGridBg(){if(!this.gridBgEl){const t=document.createElement("div");t.className=`${a}__backdrop`;const e=document.createElement("db-grid-background");e.className=`${a}__grid-bg`,e.setAttribute("width","100%"),e.setAttribute("height","100%"),t.append(e),this.gridBgEl=t,this.gridBgInner=e}return this.gridBgEl}clearBackdrop(t){"gradient"!==t&&this.gradientEl&&(this.gradientEl.remove(),this.gradientEl=void 0),"animation"!==t&&this.animBgEl&&(this.animBgEl.remove(),this.animBgEl=void 0,this.animBgInner=void 0),"grid"!==t&&this.gridBgEl&&(this.gridBgEl.remove(),this.gridBgEl=void 0,this.gridBgInner=void 0)}renderBackground(t,e){if(!this.rootEl||!this.contentEl)return;if(this.clearBackdrop(t),"gradient"===t){this.gradientEl||(this.gradientEl=document.createElement("db-random-gradient"),this.gradientEl.className=`${a}__gradient-bg`,this.gradientEl.setAttribute("height","100%"),this.gradientEl.setAttribute("width","100%"));const t=this.getAttribute("gradient-tone")||"vivid",i=!this.hasAttribute("gradient-animated")||"false"!==this.getAttribute("gradient-animated");return this.gradientEl.setAttribute("scheme",this.getAttribute("gradient-scheme")||g[e]),this.gradientEl.setAttribute("tone",t),this.gradientEl.setAttribute("animated",i?"true":"false"),this.gradientEl.parentElement!==this.rootEl&&this.rootEl.replaceChildren(this.gradientEl),void(this.contentEl.parentElement!==this.gradientEl&&this.gradientEl.append(this.contentEl))}const i=[];if("animation"===t){const t=this.ensureAnimBg();this.animBgInner?.setAttribute("type",this.getAttribute("animation-type")||"graphite"),this.animBgInner?.setAttribute("theme","dark"),i.push(t)}else if("grid"===t){const t=this.ensureGridBg();this.gridBgInner?.setAttribute("type",this.getAttribute("grid-type")||"lines"),this.gridBgInner?.setAttribute("color","var(--db-text-inverse-primary)"),this.gridBgInner?.setAttribute("opacity","0.18"),this.gridBgInner?.setAttribute("fade","vignette"),this.gridBgInner?.setAttribute("animated",""),i.push(t)}const s=[...i,this.contentEl],n=Array.from(this.rootEl.children);n.length===s.length&&s.every((t,e)=>n[e]===t)||this.rootEl.replaceChildren(...s)}syncIcon(t){this.mainEl&&this.textEl&&this.iconEl&&("card"===t?(this.iconEl.replaceChildren(i(d,32)),this.iconEl.parentElement!==this.mainEl&&this.mainEl.insertBefore(this.iconEl,this.textEl)):this.iconEl.remove())}syncBadge(t){this.textEl&&this.badgeEl&&("floating"===t?this.badgeEl.parentElement!==this.textEl&&this.textEl.insertBefore(this.badgeEl,this.textEl.firstChild):this.badgeEl.remove())}syncImage(t,e){this.contentEl&&this.imageEl&&this.mainEl&&("floating"===t&&e?(this.imageEl.src=e,this.imageEl.alt=this.getAttribute("image-alt")||"",this.imageEl.parentElement!==this.contentEl&&this.contentEl.insertBefore(this.imageEl,this.mainEl)):this.imageEl.remove())}syncLink(t,i){if(!this.contentEl)return;const n=s(i,"");if("topbar"!==t||!n)return void this.linkEl?.remove();this.linkEl||(this.linkEl=document.createElement("a"),this.linkEl.className=`${a}__link`),this.linkEl.setAttribute("href",n);const r=this.getAttribute("target");r?this.linkEl.setAttribute("target",r):this.linkEl.removeAttribute("target"),this.linkEl.setAttribute("aria-label",this.titleEl?.textContent||e("배너 링크")),this.linkEl.parentElement!==this.contentEl&&this.contentEl.append(this.linkEl)}syncCountdown(t,e){this.textEl&&this.countdownWrapEl&&(t&&e?(this.countdownEl||(this.countdownEl=document.createElement("db-countdown"),this.countdownEl.setAttribute("size","s"),this.countdownEl.setAttribute("variant","compact"),this.countdownEl.setAttribute("show-labels","false"),this.countdownWrapEl.replaceChildren(this.countdownEl)),this.countdownEl.setAttribute("end-date",e),this.countdownWrapEl.parentElement!==this.textEl&&this.textEl.append(this.countdownWrapEl)):this.countdownWrapEl.remove())}syncLocale(){const t=this.getType();this.titleEl&&!this.getAttribute("title")&&(this.titleEl.textContent=e(o[t])),this.subtitleEl&&!this.getAttribute("subtitle")&&(this.subtitleEl.textContent=e(h[t])),this.ctaEl&&!this.getAttribute("cta-text")&&(this.ctaEl.textContent=e(c[t])),this.labelEl&&(this.labelEl.textContent=e("특별 이벤트")),this.closeBtn?.setAttribute("aria-label",e("닫기")),this.linkEl?.isConnected&&this.linkEl.setAttribute("aria-label",this.titleEl?.textContent||e("배너 링크"))}updateUI(){if(!this.visible||"false"===this.getAttribute("open"))return void(this.style.display="none");this.style.display="block";const t=this.getType(),i=this.getVariant(),s=this.getSize(),n=this.getAttribute("title")||e(o[t]),r=this.getAttribute("subtitle")||e(h[t]),l=this.getAttribute("cta-text")||e(c[t]),d=this.getBool("auto-close"),g=Math.max(Number(this.getAttribute("close-delay")||5e3),1e3),E=this.getBackground(),u=this.getAttribute("gradient-tone")||"vivid",m=this.getBool("show-countdown"),b=this.getAttribute("countdown-end-date");this.rootEl&&(this.rootEl.className=[a,`${a}--type-${t}`,`${a}--variant-${i}`,`${a}--size-${s}`,`${a}--bg-${E}`,d&&`${a}--auto-close`,"gradient"===E&&`${a}--with-gradient`,"gradient"===E&&"light"===u&&`${a}--gradient-light`].filter(Boolean).join(" ")),this.renderBackground(E,i),this.syncIcon(t),this.syncBadge(t),this.syncImage(t,this.getAttribute("image")),this.titleEl&&(this.titleEl.textContent=n),this.subtitleEl&&(this.subtitleEl.textContent=r),this.syncLink(t,this.getAttribute("href")),this.syncCountdown(m,b),this.actionsEl&&(this.actionsEl.hidden="topbar"===t),this.ctaEl&&(this.ctaEl.textContent=l,this.ctaEl.setAttribute("size",this.ctaButtonSize(t,s)),"floating"===t?this.ctaEl.setAttribute("full-width",""):this.ctaEl.removeAttribute("full-width")),this.progressEl&&(this.progressEl.hidden=!d),this.progressBarEl&&d&&(this.progressBarEl.style.width=`${this.progress}%`),this.stopAutoClose(),d&&this.startAutoClose(g)}}customElements.get("db-ad-banner")||customElements.define("db-ad-banner",DbAdBanner);export{DbAdBanner};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { DbIconName } from './db-icons.js';
|
|
2
|
+
import './db-menu-item.js';
|
|
2
3
|
|
|
3
4
|
interface DbAiChatItem {
|
|
4
5
|
id: string;
|
|
@@ -23,6 +24,7 @@ interface DbAiChatItem {
|
|
|
23
24
|
* list.addEventListener('db-select', (e) => console.log(e.detail.id));
|
|
24
25
|
*/
|
|
25
26
|
declare class DbAiChatList extends HTMLElement {
|
|
27
|
+
private unsubscribeLocale?;
|
|
26
28
|
static get observedAttributes(): string[];
|
|
27
29
|
private _items;
|
|
28
30
|
get items(): DbAiChatItem[];
|
|
@@ -30,7 +32,9 @@ declare class DbAiChatList extends HTMLElement {
|
|
|
30
32
|
get selectedId(): string;
|
|
31
33
|
set selectedId(v: string);
|
|
32
34
|
connectedCallback(): void;
|
|
33
|
-
|
|
35
|
+
disconnectedCallback(): void;
|
|
36
|
+
attributeChangedCallback(name: string, previous: string | null, next: string | null): void;
|
|
37
|
+
private syncSelection;
|
|
34
38
|
private render;
|
|
35
39
|
}
|
|
36
40
|
declare global {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{setHostClasses as t}from"../core/host-class.js";import{
|
|
1
|
+
import{setHostClasses as t}from"../core/host-class.js";import{subscribeDbLocale as e,dbText as s}from"../core/i18n.js";import{isDbIconName as i}from"./db-icons.js";import"./db-menu-item.js";class DbAiChatList extends HTMLElement{constructor(){super(...arguments),this._items=[]}static get observedAttributes(){return["selected-id","new-label","title-label"]}get items(){return this._items}set items(t){this._items=Array.isArray(t)?t:[],this.render()}get selectedId(){return this.getAttribute("selected-id")||""}set selectedId(t){t?this.setAttribute("selected-id",t):this.removeAttribute("selected-id")}connectedCallback(){if(this.unsubscribeLocale?.(),this.unsubscribeLocale=e(()=>{const t=document.activeElement instanceof HTMLElement&&this.contains(document.activeElement)?document.activeElement:null,e=!!t?.closest("[data-chat-new]"),s=t?.closest("[data-chat-id]")?.dataset.chatId,i=this.querySelector(".designbase-wc-ai-chat-list__scroll")?.scrollTop??0;this.render();const c=this.querySelector(".designbase-wc-ai-chat-list__scroll");c&&(c.scrollTop=i),e?this.querySelector("[data-chat-new] button")?.focus():s&&Array.from(this.querySelectorAll("[data-chat-id]")).find(t=>t.dataset.chatId===s)?.querySelector("button")?.focus()}),!this._items.length&&this.hasAttribute("items"))try{this._items=JSON.parse(this.getAttribute("items")||"[]")}catch{this._items=[]}this.render()}disconnectedCallback(){this.unsubscribeLocale?.(),this.unsubscribeLocale=void 0}attributeChangedCallback(t,e,s){this.isConnected&&e!==s&&("selected-id"===t?this.syncSelection():this.render())}syncSelection(){const t=document.activeElement instanceof HTMLElement?document.activeElement.closest("[data-chat-id]"):null;for(const t of this.querySelectorAll("db-menu-item[data-chat-id]")){const e=t.dataset.chatId===this.selectedId;t.hasAttribute("active")!==e&&t.toggleAttribute("active",e),e&&t.querySelector("button")?.setAttribute("aria-current","true")}t&&this.contains(t)&&t.querySelector("button")?.focus({preventScroll:!0})}render(){t(this,["designbase-wc-ai-chat-list"]),this.replaceChildren();const e=document.createElement("db-menu-item");if(e.setAttribute("data-chat-new",""),e.setAttribute("label",this.getAttribute("new-label")||s("새 대화")),e.setAttribute("icon","plus"),e.setAttribute("size","m"),e.addEventListener("db-click",t=>{t.stopPropagation(),this.dispatchEvent(new CustomEvent("db-new",{bubbles:!0,composed:!0}))}),this.append(e),!this._items.length)return;const c=new Map,n=this.getAttribute("title-label")||s("최근 항목");for(const t of this._items){const e=t.group||n;c.has(e)||c.set(e,[]),c.get(e).push(t)}const a=this.selectedId,o=document.createElement("div");o.className="designbase-wc-ai-chat-list__scroll";for(const[t,e]of c){const s=document.createElement("div");s.className="designbase-wc-ai-chat-list__group";const c=document.createElement("div");c.className="designbase-wc-ai-chat-list__group-title",c.textContent=t,s.append(c);for(const t of e){const e=document.createElement("db-menu-item");e.dataset.chatId=t.id,e.setAttribute("label",t.title),e.setAttribute("size","m"),t.id===a&&e.setAttribute("active",""),t.icon&&i(t.icon)&&e.setAttribute("icon",t.icon),e.addEventListener("db-click",e=>{e.stopPropagation(),this.selectedId=t.id,this.dispatchEvent(new CustomEvent("db-select",{detail:{id:t.id,title:t.title},bubbles:!0,composed:!0}))}),s.append(e)}o.append(s)}this.append(o),this.syncSelection()}}customElements.get("db-ai-chat-list")||customElements.define("db-ai-chat-list",DbAiChatList);export{DbAiChatList};
|
|
@@ -70,6 +70,7 @@ interface DbChatMessageData {
|
|
|
70
70
|
declare class DbAiChat extends HTMLElement {
|
|
71
71
|
static get observedAttributes(): string[];
|
|
72
72
|
private built;
|
|
73
|
+
private unsubscribeLocale?;
|
|
73
74
|
private typing;
|
|
74
75
|
private _messages;
|
|
75
76
|
private _suggestions;
|
|
@@ -95,6 +96,7 @@ declare class DbAiChat extends HTMLElement {
|
|
|
95
96
|
set attachOptions(v: DbChatAttachOption[]);
|
|
96
97
|
get attachOptions(): DbChatAttachOption[];
|
|
97
98
|
connectedCallback(): void;
|
|
99
|
+
disconnectedCallback(): void;
|
|
98
100
|
attributeChangedCallback(name: string): void;
|
|
99
101
|
private build;
|
|
100
102
|
private buildArtifact;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{dbText as t,dbFormat as e}from"../core/i18n.js";import"./db-chat-welcome.js";import"./db-chat-suggestions.js";import"./db-chat-message.js";import"./db-chat-typing.js";import"./db-chat-composer.js";import{createDbIcon as i,isDbIconName as s}from"./db-icons.js";import{sanitizeHtmlFragment as a}from"../core/safe-html.js";const r="designbase-wc-ai-chat";let c=0;function el(t,e,i){const s=document.createElement(t);return e&&(s.className=e),null!=i&&(s.textContent=i),s}class DbAiChat extends HTMLElement{constructor(){super(...arguments),this.built=!1,this.typing=!1,this._messages=[],this._suggestions=[],this._tools=[],this._attachOptions=null,this.threadEl=null,this.activeArtifactId=null,this.artifactSeq=0,this.onSend=t=>{t.stopPropagation();const e=t.detail;this.dispatchEvent(new CustomEvent("db-send",{detail:e,bubbles:!0,composed:!0}))},this.onSuggestionSelect=t=>{t.stopPropagation();const e=t.detail,i=new CustomEvent("db-suggestion-select",{detail:e,bubbles:!0,composed:!0,cancelable:!0});this.dispatchEvent(i)&&(this.composer.value=e.value,this.composer.focusInput())}}static get observedAttributes(){return["greeting","title","highlight","subtitle","placeholder","orb-variant","suggestions-layout","suggestions-columns","disclaimer","voice","voice-state","voice-message","voice-disabled","attach","max-width","artifact-open","artifact-title"]}set messages(t){this._messages=Array.isArray(t)?t.map(t=>({id:`m${c+=1}`,...t})):[],this.built&&this.renderContent()}get messages(){return this._messages}set suggestions(t){this._suggestions=Array.isArray(t)?t:[],this.built&&0===this._messages.length&&this.renderContent()}get suggestions(){return this._suggestions}set tools(t){this._tools=Array.isArray(t)?t:[],this.built&&(this.composer.tools=this._tools)}get tools(){return this._tools}set attachOptions(t){this._attachOptions=Array.isArray(t)?t:null,this.built&&this._attachOptions&&(this.composer.attachOptions=this._attachOptions)}get attachOptions(){return this._attachOptions??this.composer?.attachOptions??[]}connectedCallback(){this.built||this.build(),this.sync()}attributeChangedCallback(t){this.built&&("placeholder"!==t?["voice","attach","voice-state","voice-message","voice-disabled"].includes(t)?this.syncComposerToggles():"disclaimer"!==t?"artifact-open"!==t?"artifact-title"!==t?this.sync():this.artifactTitleEl.textContent=this.getAttribute("artifact-title")||"":this.syncArtifact(!0):this.renderDock():this.composer.setAttribute("placeholder",this.getAttribute("placeholder")||""))}build(){this.built=!0,this.classList.add(r),this.replaceChildren(),this.mainEl=el("div",`${r}__main`),this.scrollEl=el("div",`${r}__scroll`),this.dockEl=el("div",`${r}__dock`),this.composer=document.createElement("db-chat-composer"),this.composer.className=`${r}__composer`,this.composer.addEventListener("db-send",this.onSend),this.composer.addEventListener("db-stop",t=>this.relay(t,"db-stop")),this.composer.addEventListener("db-tool-toggle",t=>this.relay(t,"db-tool-toggle")),this.composer.addEventListener("db-attach",t=>this.relay(t,"db-attach")),this.composer.addEventListener("db-attach-files",t=>this.relay(t,"db-attach-files")),this.composer.addEventListener("db-voice",t=>this.relay(t,"db-voice")),this.mainEl.append(this.scrollEl,this.dockEl),this.buildArtifact(),this.append(this.mainEl,this.artifactEl)}buildArtifact(){this.artifactEl=el("aside",`${r}__artifact`),this.artifactEl.setAttribute("aria-hidden","true");const e=el("div",`${r}__artifact-panel`),s=el("div",`${r}__artifact-header`),a=el("div",`${r}__artifact-titles`);this.artifactTitleEl=el("div",`${r}__artifact-title`),this.artifactSubtitleEl=el("div",`${r}__artifact-subtitle`),this.artifactSubtitleEl.hidden=!0,a.append(this.artifactTitleEl,this.artifactSubtitleEl);const c=el("button",`${r}__artifact-close`);c.type="button",c.setAttribute("aria-label",t("아티팩트 닫기")),c.append(i("close",18)),c.addEventListener("click",()=>this.closeArtifact()),s.append(a,c),this.artifactBodyEl=el("div",`${r}__artifact-body`),e.append(s,this.artifactBodyEl),this.artifactEl.append(e)}sync(){const t=this.getAttribute("max-width")||"100%";this.style.setProperty("--dbchat-max",t),this.syncComposerToggles(),this.composer.setAttribute("placeholder",this.getAttribute("placeholder")||"무엇이든 물어보세요…"),this._tools.length&&(this.composer.tools=this._tools),this._attachOptions&&(this.composer.attachOptions=this._attachOptions);const e=this.getAttribute("artifact-title");null!=e&&(this.artifactTitleEl.textContent=e),this.renderContent(),this.renderDock(),this.syncArtifact(!1)}syncArtifact(t){const e=this.hasAttribute("artifact-open");this.classList.toggle(`${r}--artifact-open`,e),this.artifactEl.setAttribute("aria-hidden",String(!e)),this.artifactEl.toggleAttribute("inert",!e),e||(this.activeArtifactId=null),this.syncArtifactCardStates(),t&&this.dispatchEvent(new CustomEvent(e?"db-artifact-open":"db-artifact-close",{detail:e?{id:this.activeArtifactId}:{id:null},bubbles:!0,composed:!0}))}syncComposerToggles(){this.composer.toggleAttribute("voice",this.hasAttribute("voice")),this.composer.setAttribute("voice-state",this.getAttribute("voice-state")||"idle"),this.composer.setAttribute("voice-message",this.getAttribute("voice-message")||""),this.composer.toggleAttribute("voice-disabled",this.hasAttribute("voice-disabled")),this.composer.toggleAttribute("attach","false"!==this.getAttribute("attach"))}renderContent(){this.scrollEl.replaceChildren(),this.threadEl=null;const t=0===this._messages.length;this.classList.toggle(`${r}--empty`,t),t?this.renderHero():this.renderThread()}renderHero(){const t=el("div",`${r}__hero`),e=document.createElement("db-chat-welcome");if(e.className=`${r}__welcome`,this.getAttribute("greeting")&&e.setAttribute("greeting",this.getAttribute("greeting")),e.setAttribute("title",this.getAttribute("title")||"무엇을 도와드릴까요?"),this.getAttribute("highlight")&&e.setAttribute("highlight",this.getAttribute("highlight")),this.getAttribute("subtitle")&&e.setAttribute("subtitle",this.getAttribute("subtitle")),e.setAttribute("orb-variant",this.getAttribute("orb-variant")||"aurora"),e.setAttribute("align","center"),t.append(e),this._suggestions.length){const e=document.createElement("db-chat-suggestions");e.className=`${r}__suggestions`,e.setAttribute("layout",this.getAttribute("suggestions-layout")||"card"),e.setAttribute("columns",this.getAttribute("suggestions-columns")||"3"),e.items=this._suggestions,e.addEventListener("db-select",this.onSuggestionSelect),t.append(e)}this.scrollEl.append(t)}renderThread(){const t=el("div",`${r}__thread`);this._messages.forEach(e=>t.append(this.buildMessage(e))),this.threadEl=t,this.scrollEl.append(t),this.typing&&t.append(this.buildTyping()),requestAnimationFrame(()=>this.scrollToEnd())}buildMessage(t){const e=el("div",`${r}__turn`);e.dataset.id=t.id||"";const i=el("db-chat-message");return i.dataset.id=t.id||"",i.setAttribute("role",t.role||"assistant"),t.name&&i.setAttribute("name",t.name),t.time&&i.setAttribute("timestamp",t.time),t.avatarSrc&&i.setAttribute("avatar-src",t.avatarSrc),t.avatarInitials&&i.setAttribute("avatar-initials",t.avatarInitials),t.actions&&i.setAttribute("actions",t.actions),t.status&&i.setAttribute("status",t.status),t.variant&&i.setAttribute("variant",t.variant),null!=t.html?i.replaceChildren(a(t.html)):i.textContent=t.text||"",i.addEventListener("db-action",t=>this.relay(t,"db-message-action")),e.append(i),t.artifact&&(this.ensureArtifactId(t.artifact),e.append(this.buildArtifactCard(t.artifact))),e}ensureArtifactId(t){return t.id||(t.id=`art-${this.artifactSeq+=1}`),t.id}buildArtifactCard(t){const a=this.ensureArtifactId(t),c=el("button",`${r}__artifact-card`);c.type="button",c.dataset.artifactId=a,c.setAttribute("aria-pressed","false"),c.setAttribute("aria-label",e("{0} 아티팩트 열기",[t.title]));const n=el("span",`${r}__artifact-card-icon`),l=t.icon&&s(t.icon)?t.icon:"code";n.append(i(l,18));const o=el("div",`${r}__artifact-card-meta`);o.append(el("div",`${r}__artifact-card-title`,t.title)),t.subtitle&&o.append(el("div",`${r}__artifact-card-subtitle`,t.subtitle));const h=el("span",`${r}__artifact-card-chevron`);return h.append(i("chevron-right",16)),c.append(n,o,h),c.addEventListener("click",()=>this.toggleArtifactFromCard(t)),c}syncArtifactCardStates(){this.querySelectorAll(`.${r}__artifact-card`).forEach(t=>{const e=t,i=this.artifactOpen&&e.dataset.artifactId===this.activeArtifactId;e.classList.toggle(`${r}__artifact-card--active`,i),e.setAttribute("aria-pressed",String(i))})}toggleArtifactFromCard(t){const e=this.ensureArtifactId(t);this.artifactOpen&&this.activeArtifactId===e?this.closeArtifact():this.openArtifact({id:e,title:t.title,subtitle:t.subtitle,html:t.html})}buildTyping(){const t=el("db-chat-typing",`${r}__typing`),e=this.getAttribute("title")||"AI";return t.setAttribute("name",this.getAttribute("agent-name")||e),t.setAttribute("label","생각 중…"),t.setAttribute("avatar-initials","AI"),t}renderDock(){this.dockEl.replaceChildren(),this.dockEl.append(this.composer);const t=this.getAttribute("disclaimer");t&&this.dockEl.append(el("p",`${r}__disclaimer`,t))}relay(t,e){t.stopPropagation(),this.dispatchEvent(new CustomEvent(e,{detail:t.detail,bubbles:!0,composed:!0}))}scrollToEnd(){this.scrollEl.scrollTop=this.scrollEl.scrollHeight}addMessage(t){const e={id:`m${c+=1}`,...t},i=0===this._messages.length;if(this._messages.push(e),i||!this.threadEl)this.renderContent();else{const t=this.threadEl.querySelector("db-chat-typing"),i=this.buildMessage(e);t?this.threadEl.insertBefore(i,t):this.threadEl.append(i),requestAnimationFrame(()=>this.scrollToEnd())}return e.id}updateMessage(t,e){const i=this._messages.find(e=>e.id===t);if(!i)return;Object.assign(i,e);const s=this.threadEl?.querySelector(`db-chat-message[data-id="${t}"]`);if(!s)return;const c=s.querySelector(".designbase-wc-chat-message__content");if(!c||null==e.html&&null==e.text||(null!=e.html?c.replaceChildren(a(e.html)):c.textContent=e.text||""),e.status&&s.setAttribute("status",e.status),void 0!==e.artifact){const t=s.closest(`.${r}__turn`)||s.parentElement;t?.querySelector(`.${r}__artifact-card`)?.remove(),e.artifact&&t&&(this.ensureArtifactId(e.artifact),t.append(this.buildArtifactCard(e.artifact)),this.syncArtifactCardStates())}requestAnimationFrame(()=>this.scrollToEnd())}appendToMessage(t,e){const i=this._messages.find(e=>e.id===t);if(!i)return;i.text=(i.text||"")+e;const s=this.threadEl?.querySelector(`db-chat-message[data-id="${t}"]`),a=s?.querySelector(".designbase-wc-chat-message__content");a&&(a.textContent=i.text),requestAnimationFrame(()=>this.scrollToEnd())}setMessages(t){this.messages=t}clear(){this._messages=[],this.typing=!1,this.closeArtifact(),this.built&&this.renderContent()}setTyping(t){if(this.typing=Boolean(t),!this.built||!this.threadEl)return;const e=this.threadEl.querySelector("db-chat-typing");this.typing&&!e?(this.threadEl.append(this.buildTyping()),requestAnimationFrame(()=>this.scrollToEnd())):!this.typing&&e&&e.remove()}setStreaming(t){this.composer?.setStreaming(Boolean(t))}focusInput(){this.composer?.focusInput()}getComposer(){return this.composer}get artifactOpen(){return this.hasAttribute("artifact-open")}set artifactOpen(t){this.toggleAttribute("artifact-open",t)}openArtifact(t={}){null!=t.title&&(this.artifactTitleEl.textContent=t.title),null!=t.subtitle&&(this.artifactSubtitleEl.textContent=t.subtitle,this.artifactSubtitleEl.hidden=!t.subtitle),null!=t.html&&this.artifactBodyEl.replaceChildren(a(t.html)),null!=t.id&&(this.activeArtifactId=t.id);const e=this.artifactOpen;this.artifactOpen=!0,e&&this.syncArtifactCardStates()}closeArtifact(){this.activeArtifactId=null;const t=this.artifactOpen;this.artifactOpen=!1,t||this.syncArtifactCardStates()}toggleArtifact(){this.artifactOpen?this.closeArtifact():this.artifactOpen=!0}getArtifactBody(){return this.artifactBodyEl}setArtifactContent(t){this.artifactBodyEl.replaceChildren(t)}}customElements.get("db-ai-chat")||customElements.define("db-ai-chat",DbAiChat);export{DbAiChat};
|
|
1
|
+
import{subscribeDbLocale as t,dbText as e,dbFormat as i}from"../core/i18n.js";import"./db-chat-welcome.js";import"./db-chat-suggestions.js";import"./db-chat-message.js";import"./db-chat-typing.js";import"./db-chat-composer.js";import{createDbIcon as s,isDbIconName as a}from"./db-icons.js";import{sanitizeHtmlFragment as r}from"../core/safe-html.js";const c="designbase-wc-ai-chat";let l=0;function el(t,e,i){const s=document.createElement(t);return e&&(s.className=e),null!=i&&(s.textContent=i),s}class DbAiChat extends HTMLElement{constructor(){super(...arguments),this.built=!1,this.typing=!1,this._messages=[],this._suggestions=[],this._tools=[],this._attachOptions=null,this.threadEl=null,this.activeArtifactId=null,this.artifactSeq=0,this.onSend=t=>{t.stopPropagation();const e=t.detail;this.dispatchEvent(new CustomEvent("db-send",{detail:e,bubbles:!0,composed:!0}))},this.onSuggestionSelect=t=>{t.stopPropagation();const e=t.detail,i=new CustomEvent("db-suggestion-select",{detail:e,bubbles:!0,composed:!0,cancelable:!0});this.dispatchEvent(i)&&(this.composer.value=e.value,this.composer.focusInput())}}static get observedAttributes(){return["greeting","title","highlight","subtitle","placeholder","orb-variant","suggestions-layout","suggestions-columns","disclaimer","voice","voice-state","voice-message","voice-disabled","attach","max-width","artifact-open","artifact-title"]}set messages(t){this._messages=Array.isArray(t)?t.map(t=>({id:`m${l+=1}`,...t})):[],this.built&&this.renderContent()}get messages(){return this._messages}set suggestions(t){this._suggestions=Array.isArray(t)?t:[],this.built&&0===this._messages.length&&this.renderContent()}get suggestions(){return this._suggestions}set tools(t){this._tools=Array.isArray(t)?t:[],this.built&&(this.composer.tools=this._tools)}get tools(){return this._tools}set attachOptions(t){this._attachOptions=Array.isArray(t)?t:null,this.built&&this._attachOptions&&(this.composer.attachOptions=this._attachOptions)}get attachOptions(){return this._attachOptions??this.composer?.attachOptions??[]}connectedCallback(){this.built||this.build(),this.unsubscribeLocale?.(),this.unsubscribeLocale=t(()=>{this.hasAttribute("placeholder")||this.composer.setAttribute("placeholder",e("무엇이든 물어보세요…")),this.hasAttribute("title")||this.querySelector(`.${c}__welcome`)?.setAttribute("title",e("무엇을 도와드릴까요?")),this.artifactEl.querySelector(`.${c}__artifact-close`)?.setAttribute("aria-label",e("아티팩트 닫기"))}),this.sync()}disconnectedCallback(){this.unsubscribeLocale?.(),this.unsubscribeLocale=void 0}attributeChangedCallback(t){this.built&&("placeholder"!==t?["voice","attach","voice-state","voice-message","voice-disabled"].includes(t)?this.syncComposerToggles():"disclaimer"!==t?"artifact-open"!==t?"artifact-title"!==t?this.sync():this.artifactTitleEl.textContent=this.getAttribute("artifact-title")||"":this.syncArtifact(!0):this.renderDock():this.composer.setAttribute("placeholder",this.getAttribute("placeholder")||e("무엇이든 물어보세요…")))}build(){this.built=!0,this.classList.add(c),this.replaceChildren(),this.mainEl=el("div",`${c}__main`),this.scrollEl=el("div",`${c}__scroll`),this.dockEl=el("div",`${c}__dock`),this.composer=document.createElement("db-chat-composer"),this.composer.className=`${c}__composer`,this.composer.addEventListener("db-send",this.onSend),this.composer.addEventListener("db-stop",t=>this.relay(t,"db-stop")),this.composer.addEventListener("db-tool-toggle",t=>this.relay(t,"db-tool-toggle")),this.composer.addEventListener("db-attach",t=>this.relay(t,"db-attach")),this.composer.addEventListener("db-attach-files",t=>this.relay(t,"db-attach-files")),this.composer.addEventListener("db-voice",t=>this.relay(t,"db-voice")),this.mainEl.append(this.scrollEl,this.dockEl),this.buildArtifact(),this.append(this.mainEl,this.artifactEl)}buildArtifact(){this.artifactEl=el("aside",`${c}__artifact`),this.artifactEl.setAttribute("aria-hidden","true");const t=el("div",`${c}__artifact-panel`),i=el("div",`${c}__artifact-header`),a=el("div",`${c}__artifact-titles`);this.artifactTitleEl=el("div",`${c}__artifact-title`),this.artifactSubtitleEl=el("div",`${c}__artifact-subtitle`),this.artifactSubtitleEl.hidden=!0,a.append(this.artifactTitleEl,this.artifactSubtitleEl);const r=el("button",`${c}__artifact-close`);r.type="button",r.setAttribute("aria-label",e("아티팩트 닫기")),r.append(s("close",18)),r.addEventListener("click",()=>this.closeArtifact()),i.append(a,r),this.artifactBodyEl=el("div",`${c}__artifact-body`),t.append(i,this.artifactBodyEl),this.artifactEl.append(t)}sync(){const t=this.getAttribute("max-width")||"100%";this.style.setProperty("--dbchat-max",t),this.syncComposerToggles(),this.composer.setAttribute("placeholder",this.getAttribute("placeholder")||e("무엇이든 물어보세요…")),this._tools.length&&(this.composer.tools=this._tools),this._attachOptions&&(this.composer.attachOptions=this._attachOptions);const i=this.getAttribute("artifact-title");null!=i&&(this.artifactTitleEl.textContent=i),this.renderContent(),this.renderDock(),this.syncArtifact(!1)}syncArtifact(t){const e=this.hasAttribute("artifact-open");this.classList.toggle(`${c}--artifact-open`,e),this.artifactEl.setAttribute("aria-hidden",String(!e)),this.artifactEl.toggleAttribute("inert",!e),e||(this.activeArtifactId=null),this.syncArtifactCardStates(),t&&this.dispatchEvent(new CustomEvent(e?"db-artifact-open":"db-artifact-close",{detail:e?{id:this.activeArtifactId}:{id:null},bubbles:!0,composed:!0}))}syncComposerToggles(){this.composer.toggleAttribute("voice",this.hasAttribute("voice")),this.composer.setAttribute("voice-state",this.getAttribute("voice-state")||"idle"),this.composer.setAttribute("voice-message",this.getAttribute("voice-message")||""),this.composer.toggleAttribute("voice-disabled",this.hasAttribute("voice-disabled")),this.composer.toggleAttribute("attach","false"!==this.getAttribute("attach"))}renderContent(){this.scrollEl.replaceChildren(),this.threadEl=null;const t=0===this._messages.length;this.classList.toggle(`${c}--empty`,t),t?this.renderHero():this.renderThread()}renderHero(){const t=el("div",`${c}__hero`),i=document.createElement("db-chat-welcome");if(i.className=`${c}__welcome`,this.getAttribute("greeting")&&i.setAttribute("greeting",this.getAttribute("greeting")),i.setAttribute("title",this.getAttribute("title")||e("무엇을 도와드릴까요?")),this.getAttribute("highlight")&&i.setAttribute("highlight",this.getAttribute("highlight")),this.getAttribute("subtitle")&&i.setAttribute("subtitle",this.getAttribute("subtitle")),i.setAttribute("orb-variant",this.getAttribute("orb-variant")||"aurora"),i.setAttribute("align","center"),t.append(i),this._suggestions.length){const e=document.createElement("db-chat-suggestions");e.className=`${c}__suggestions`,e.setAttribute("layout",this.getAttribute("suggestions-layout")||"card"),e.setAttribute("columns",this.getAttribute("suggestions-columns")||"3"),e.items=this._suggestions,e.addEventListener("db-select",this.onSuggestionSelect),t.append(e)}this.scrollEl.append(t)}renderThread(){const t=el("div",`${c}__thread`);this._messages.forEach(e=>t.append(this.buildMessage(e))),this.threadEl=t,this.scrollEl.append(t),this.typing&&t.append(this.buildTyping()),requestAnimationFrame(()=>this.scrollToEnd())}buildMessage(t){const e=el("div",`${c}__turn`);e.dataset.id=t.id||"";const i=el("db-chat-message");return i.dataset.id=t.id||"",i.setAttribute("role",t.role||"assistant"),t.name&&i.setAttribute("name",t.name),t.time&&i.setAttribute("timestamp",t.time),t.avatarSrc&&i.setAttribute("avatar-src",t.avatarSrc),t.avatarInitials&&i.setAttribute("avatar-initials",t.avatarInitials),t.actions&&i.setAttribute("actions",t.actions),t.status&&i.setAttribute("status",t.status),t.variant&&i.setAttribute("variant",t.variant),null!=t.html?i.replaceChildren(r(t.html)):i.textContent=t.text||"",i.addEventListener("db-action",t=>this.relay(t,"db-message-action")),e.append(i),t.artifact&&(this.ensureArtifactId(t.artifact),e.append(this.buildArtifactCard(t.artifact))),e}ensureArtifactId(t){return t.id||(t.id=`art-${this.artifactSeq+=1}`),t.id}buildArtifactCard(t){const e=this.ensureArtifactId(t),r=el("button",`${c}__artifact-card`);r.type="button",r.dataset.artifactId=e,r.setAttribute("aria-pressed","false"),r.setAttribute("aria-label",i("{0} 아티팩트 열기",[t.title]));const l=el("span",`${c}__artifact-card-icon`),n=t.icon&&a(t.icon)?t.icon:"code";l.append(s(n,18));const o=el("div",`${c}__artifact-card-meta`);o.append(el("div",`${c}__artifact-card-title`,t.title)),t.subtitle&&o.append(el("div",`${c}__artifact-card-subtitle`,t.subtitle));const h=el("span",`${c}__artifact-card-chevron`);return h.append(s("chevron-right",16)),r.append(l,o,h),r.addEventListener("click",()=>this.toggleArtifactFromCard(t)),r}syncArtifactCardStates(){this.querySelectorAll(`.${c}__artifact-card`).forEach(t=>{const e=t,i=this.artifactOpen&&e.dataset.artifactId===this.activeArtifactId;e.classList.toggle(`${c}__artifact-card--active`,i),e.setAttribute("aria-pressed",String(i))})}toggleArtifactFromCard(t){const e=this.ensureArtifactId(t);this.artifactOpen&&this.activeArtifactId===e?this.closeArtifact():this.openArtifact({id:e,title:t.title,subtitle:t.subtitle,html:t.html})}buildTyping(){const t=el("db-chat-typing",`${c}__typing`),e=this.getAttribute("title")||"AI";return t.setAttribute("name",this.getAttribute("agent-name")||e),t.setAttribute("label","생각 중…"),t.setAttribute("avatar-initials","AI"),t}renderDock(){this.dockEl.replaceChildren(),this.dockEl.append(this.composer);const t=this.getAttribute("disclaimer");t&&this.dockEl.append(el("p",`${c}__disclaimer`,t))}relay(t,e){t.stopPropagation(),this.dispatchEvent(new CustomEvent(e,{detail:t.detail,bubbles:!0,composed:!0}))}scrollToEnd(){this.scrollEl.scrollTop=this.scrollEl.scrollHeight}addMessage(t){const e={id:`m${l+=1}`,...t},i=0===this._messages.length;if(this._messages.push(e),i||!this.threadEl)this.renderContent();else{const t=this.threadEl.querySelector("db-chat-typing"),i=this.buildMessage(e);t?this.threadEl.insertBefore(i,t):this.threadEl.append(i),requestAnimationFrame(()=>this.scrollToEnd())}return e.id}updateMessage(t,e){const i=this._messages.find(e=>e.id===t);if(!i)return;Object.assign(i,e);const s=this.threadEl?.querySelector(`db-chat-message[data-id="${t}"]`);if(!s)return;const a=s.querySelector(".designbase-wc-chat-message__content");if(!a||null==e.html&&null==e.text||(null!=e.html?a.replaceChildren(r(e.html)):a.textContent=e.text||""),e.status&&s.setAttribute("status",e.status),void 0!==e.artifact){const t=s.closest(`.${c}__turn`)||s.parentElement;t?.querySelector(`.${c}__artifact-card`)?.remove(),e.artifact&&t&&(this.ensureArtifactId(e.artifact),t.append(this.buildArtifactCard(e.artifact)),this.syncArtifactCardStates())}requestAnimationFrame(()=>this.scrollToEnd())}appendToMessage(t,e){const i=this._messages.find(e=>e.id===t);if(!i)return;i.text=(i.text||"")+e;const s=this.threadEl?.querySelector(`db-chat-message[data-id="${t}"]`),a=s?.querySelector(".designbase-wc-chat-message__content");a&&(a.textContent=i.text),requestAnimationFrame(()=>this.scrollToEnd())}setMessages(t){this.messages=t}clear(){this._messages=[],this.typing=!1,this.closeArtifact(),this.built&&this.renderContent()}setTyping(t){if(this.typing=Boolean(t),!this.built||!this.threadEl)return;const e=this.threadEl.querySelector("db-chat-typing");this.typing&&!e?(this.threadEl.append(this.buildTyping()),requestAnimationFrame(()=>this.scrollToEnd())):!this.typing&&e&&e.remove()}setStreaming(t){this.composer?.setStreaming(Boolean(t))}focusInput(){this.composer?.focusInput()}getComposer(){return this.composer}get artifactOpen(){return this.hasAttribute("artifact-open")}set artifactOpen(t){this.toggleAttribute("artifact-open",t)}openArtifact(t={}){null!=t.title&&(this.artifactTitleEl.textContent=t.title),null!=t.subtitle&&(this.artifactSubtitleEl.textContent=t.subtitle,this.artifactSubtitleEl.hidden=!t.subtitle),null!=t.html&&this.artifactBodyEl.replaceChildren(r(t.html)),null!=t.id&&(this.activeArtifactId=t.id);const e=this.artifactOpen;this.artifactOpen=!0,e&&this.syncArtifactCardStates()}closeArtifact(){this.activeArtifactId=null;const t=this.artifactOpen;this.artifactOpen=!1,t||this.syncArtifactCardStates()}toggleArtifact(){this.artifactOpen?this.closeArtifact():this.artifactOpen=!0}getArtifactBody(){return this.artifactBodyEl}setArtifactContent(t){this.artifactBodyEl.replaceChildren(t)}}customElements.get("db-ai-chat")||customElements.define("db-ai-chat",DbAiChat);export{DbAiChat};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{t as e}from"../core/i18n.js";import{DbAiDataElement as t,aiNode as i}from"../core/ai-element.js";class DbAiFileTree extends t{static get observedAttributes(){return["label","items","disabled","selected-id"]}view(){const build=(t,r=[])=>{const s=i("ul","designbase-wc-ai__files","");for(const e of t){const t=document.createElement("li");if(e.children){const i=this.disclosure(`folder:${[...r,e.id].map(encodeURIComponent).join("/")}`,e.label,"complete",[build(e.children,[...r,e.id])],!0,"folder"),s=i.querySelector(".designbase-wc-ai__icon");s?.removeAttribute("aria-label"),s?.setAttribute("aria-hidden","true");const
|
|
1
|
+
import{t as e}from"../core/i18n.js";import{DbAiDataElement as t,aiNode as i}from"../core/ai-element.js";class DbAiFileTree extends t{static get observedAttributes(){return["label","items","disabled","selected-id"]}view(){const build=(t,r=[])=>{const s=i("ul","designbase-wc-ai__files","");for(const e of t){const t=document.createElement("li");if(e.children){const i=this.disclosure(`folder:${[...r,e.id].map(encodeURIComponent).join("/")}`,e.label,"complete",[build(e.children,[...r,e.id])],!0,"folder"),s=i.querySelector(".designbase-wc-ai__icon");s?.removeAttribute("aria-label"),s?.setAttribute("aria-hidden","true");const n=i.querySelector("summary"),l=n.lastElementChild;l.classList.add("designbase-wc-ai__folder-chevron"),n.prepend(l),t.append(i)}else{const i=this.button("file-open",e.label,"file-new",e.id),r=document.createElement("span");r.className="designbase-wc-ai__file-name",r.textContent=e.label,i.title=e.label,Array.from(i.childNodes).filter(e=>e.nodeType===Node.TEXT_NODE&&e.textContent?.trim()).forEach(e=>e.remove()),i.append(r),e.id===this.getAttribute("selected-id")&&(i.setAttribute("variant","secondary"),i.setAttribute("aria-current","true")),t.append(i)}s.append(t)}return t.length||s.append(i("li","designbase-wc-ai__empty-folder",e("aiElement.emptyFolder"))),s};if(!this.rows.length)return[i("p","designbase-wc-ai__description",e("aiElement.noFiles"))];const t=i("div","designbase-wc-ai__file-tree","");return t.setAttribute("aria-label",this.attr("label","files")),t.append(build(this.rows)),[t]}}customElements.get("db-ai-file-tree")||customElements.define("db-ai-file-tree",DbAiFileTree);export{DbAiFileTree};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{dbText as
|
|
1
|
+
import{subscribeDbLocale as t,dbText as e}from"../core/i18n.js";import{setHostClasses as s}from"../core/host-class.js";import"./db-button.js";import{createDbIcon as i}from"./db-icons.js";import{coerceToContract as n}from"../node_modules/@designbasekorea/ui-contracts/dist/primitives.js";import{ALERT_VARIANTS as o,ALERT_SIZES as r}from"../node_modules/@designbasekorea/ui-contracts/dist/components.js";const a="designbase-wc-alert";class DbAlert extends HTMLElement{constructor(){super(...arguments),this.renderedSlotted=!1}static get observedAttributes(){return["title","message","variant","size","show-icon","closable","action-label","action-variant","action-buttons"]}connectedCallback(){this.render(),this.unsubscribeLocale?.(),this.unsubscribeLocale=t(()=>this.querySelector(`.${a}__close`)?.setAttribute("aria-label",e("알림 닫기"))),this.observer??(this.observer=new MutationObserver(()=>this.adoptChildren())),this.observer.observe(this,{childList:!0}),this.slotObserver??(this.slotObserver=new MutationObserver(()=>{this.hasSlotted()!==this.renderedSlotted&&this.render()})),this.slotObserver.observe(this.getSlot(),{childList:!0,subtree:!0,characterData:!0})}disconnectedCallback(){this.unsubscribeLocale?.(),this.unsubscribeLocale=void 0,this.observer?.disconnect(),this.slotObserver?.disconnect()}attributeChangedCallback(){this.isConnected&&this.render()}getSlot(){return this.slotEl||(this.slotEl=document.createElement("div"),this.slotEl.className=`${a}__slot`,this.slotEl.setAttribute("data-wc-internal","")),this.slotEl}moveChildrenToSlot(){const t=this.getSlot();let e=!1;for(const s of Array.from(this.childNodes))s instanceof Element&&s.hasAttribute("data-wc-internal")||s.nodeType!==Node.COMMENT_NODE&&(s.nodeType!==Node.TEXT_NODE||s.textContent?.trim()?(t.append(s),e=!0):s.remove());return e}adoptChildren(){this.moveChildrenToSlot()&&this.render()}hasSlotted(){const hasContent=t=>t.nodeType===Node.TEXT_NODE?Boolean(t.textContent?.trim()):t instanceof Element&&(!t.hasAttribute("data-db-children")||Array.from(t.childNodes).some(hasContent));return Boolean(this.slotEl&&Array.from(this.slotEl.childNodes).some(hasContent))}getIconName(t){return"success"===t?"circle-checked":"warning"===t?"warning-filled":"danger"===t?"error-filled":"info-filled"}parseActionButtons(){const t=this.getAttribute("action-buttons");if(!t)return[];try{const e=JSON.parse(t);return Array.isArray(e)?e:[]}catch{return[]}}render(){const t=n(this.getAttribute("variant"),o,"default"),l=n(this.getAttribute("size"),r,"m"),c="false"!==this.getAttribute("show-icon"),d=this.hasAttribute("closable")&&"false"!==this.getAttribute("closable"),b=this.getAttribute("title")?.trim()||"",h=this.getAttribute("message")?.trim()||"",u=this.getAttribute("action-label"),m=this.getAttribute("action-variant")||"tertiary",p=this.parseActionButtons(),v=Boolean(h),E=Boolean(b);this.moveChildrenToSlot();const A=this.hasSlotted();this.renderedSlotted=A;const g=1===Number(E)+Number(v)+Number(A),f=p.length>0?p:u?[{label:u,variant:m}]:[];s(this,[a,`${a}--${t}`,`${a}--${l}`,c&&`${a}--with-icon`,d&&`${a}--closable`,g&&`${a}--text-single`,f.length>0&&`${a}--has-actions`]),this.setAttribute("role","alert"),this.setAttribute("aria-live","polite");const N=this.getSlot(),_=document.createElement("div");if(_.className=`${a}__content`,_.setAttribute("data-wc-internal",""),c){const e=document.createElement("div");e.className=`${a}__icon`,e.append(i(this.getIconName(t),"s"===l?16:"l"===l?24:20)),_.append(e)}const C=document.createElement("div");if(C.className=`${a}__body`,E){const t=document.createElement("div");t.className=`${a}__title`,t.textContent=b,C.append(t)}if(v){const t=document.createElement("div");t.className=`${a}__message`,t.textContent=h,C.append(t)}if(A?C.append(N):N.remove(),_.append(C),f.length>0){const t=document.createElement("div");t.className=`${a}__actions`,t.setAttribute("data-wc-internal","");const e=document.createElement("div");e.className=`${a}__action-buttons`,f.forEach((t,s)=>{const i=document.createElement("db-button");i.setAttribute("variant",t.variant||"tertiary"),i.setAttribute("size",t.size||l),i.textContent=t.label,t.disabled&&i.setAttribute("disabled",""),i.addEventListener("db-click",()=>{this.dispatchEvent(new CustomEvent("db-action",{bubbles:!0,composed:!0,detail:{index:s,label:t.label}}))}),e.append(i)}),t.append(e),_.append(t)}if(d){const t=document.createElement("button");t.type="button",t.className=`${a}__close`,t.setAttribute("aria-label",e("알림 닫기")),t.append(i("close",20)),t.addEventListener("click",()=>{this.hidden=!0,this.dispatchEvent(new CustomEvent("db-close",{bubbles:!0,composed:!0}))}),_.append(t)}this.observer?.disconnect(),this.replaceChildren(_),this.isConnected&&this.observer?.observe(this,{childList:!0})}}customElements.get("db-alert")||customElements.define("db-alert",DbAlert);export{DbAlert};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
declare class DbAudioPlayer extends HTMLElement {
|
|
2
2
|
static get observedAttributes(): string[];
|
|
3
3
|
private built;
|
|
4
|
+
private unsubscribeLocale?;
|
|
4
5
|
private audioEl?;
|
|
5
6
|
private progressEl?;
|
|
6
7
|
private progressBarEl?;
|
|
@@ -75,6 +76,7 @@ declare class DbAudioPlayer extends HTMLElement {
|
|
|
75
76
|
private rebuildControlButtons;
|
|
76
77
|
private rebuildPlaylist;
|
|
77
78
|
private updatePlaylistActive;
|
|
79
|
+
private refreshLocale;
|
|
78
80
|
private updateUI;
|
|
79
81
|
}
|
|
80
82
|
declare global {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{dbText as t,t as e}from"../core/i18n.js";import{createDbIcon as s}from"./db-icons.js";import{coerceToContract as i}from"../node_modules/@designbasekorea/ui-contracts/dist/primitives.js";import{AUDIO_PLAYER_SIZES as l,AUDIO_PLAYER_VARIANTS as a}from"../node_modules/@designbasekorea/ui-contracts/dist/components.js";const n="designbase-wc-audio-player";class DbAudioPlayer extends HTMLElement{constructor(){super(...arguments),this.built=!1,this.isPlaying=!1,this.isMuted=!1,this.volume=1,this.currentTime=0,this.duration=0,this.showSettingsMenu=!1,this.playbackRate=1,this.currentRepeatMode="none",this.isShuffle=!1,this.isLoading=!1,this.error=null,this.playlistIndex=0,this.shuffledPlaylist=[],this.boundKeyDown=t=>this.handleKeyDown(t)}static get observedAttributes(){return["src","title","artist","album","album-art","size","variant","theme","auto-play","loop","muted","show-controls","enable-keyboard","show-progress","show-time","show-volume","show-settings","playlist","current-index","playback-rates","default-playback-rate","repeat-mode","shuffle"]}connectedCallback(){this.style.display="block",this.built||(this.built=!0,this.build(),this.bindAudioEvents()),this.syncFromAttributes(),this.updateUI(),this.getBool("enable-keyboard",!0)&&document.addEventListener("keydown",this.boundKeyDown)}disconnectedCallback(){document.removeEventListener("keydown",this.boundKeyDown)}attributeChangedCallback(t){if(this.built){if("current-index"===t)return this.playlistIndex=Number(this.getAttribute("current-index")||0),void this.loadCurrentTrack();"repeat-mode"===t&&(this.currentRepeatMode=this.getAttribute("repeat-mode")||"none"),"shuffle"===t&&(this.isShuffle=this.getBool("shuffle"),this.refreshShuffledPlaylist()),"default-playback-rate"===t&&(this.playbackRate=Number(this.getAttribute("default-playback-rate")||1),this.audioEl&&(this.audioEl.playbackRate=this.playbackRate)),this.updateUI()}}getBool(t,e=!1){if(!this.hasAttribute(t))return e;const s=this.getAttribute(t);return""===s||"true"===s}getPlaylist(){try{const t=this.getAttribute("playlist"),e=t?JSON.parse(t):[];return Array.isArray(e)?e:[]}catch{return[]}}getPlaybackRates(){try{const t=this.getAttribute("playback-rates"),e=t?JSON.parse(t):[.5,.75,1,1.25,1.5,2];return Array.isArray(e)&&e.length>0?e:[.5,.75,1,1.25,1.5,2]}catch{return[.5,.75,1,1.25,1.5,2]}}getIconSize(){const t=i(this.getAttribute("size"),l,"m");return"s"===t?16:"l"===t?24:"xl"===t?32:20}getCurrentTrack(){const t=this.getPlaylist();if(t.length>0){return t[this.isShuffle&&this.shuffledPlaylist.length>0?this.shuffledPlaylist[this.playlistIndex]??this.playlistIndex:this.playlistIndex]||t[0]}return{src:this.getAttribute("src")||"",title:this.getAttribute("title")||void 0,artist:this.getAttribute("artist")||void 0,album:this.getAttribute("album")||void 0,albumArt:this.getAttribute("album-art")||void 0}}refreshShuffledPlaylist(){const t=this.getPlaylist();if(t.length>0&&this.isShuffle){const e=Array.from({length:t.length},(t,e)=>e);this.shuffledPlaylist=[...e].sort(()=>Math.random()-.5)}else this.shuffledPlaylist=[]}formatTime(t){return`${Math.floor(t/60)}:${Math.floor(t%60).toString().padStart(2,"0")}`}syncFromAttributes(){this.playlistIndex=Number(this.getAttribute("current-index")||0),this.currentRepeatMode=this.getAttribute("repeat-mode")||"none",this.isShuffle=this.getBool("shuffle"),this.playbackRate=Number(this.getAttribute("default-playback-rate")||1),this.isMuted=this.getBool("muted"),this.volume=this.isMuted?0:1,this.refreshShuffledPlaylist(),this.loadCurrentTrack()}loadCurrentTrack(){if(!this.audioEl)return;const t=this.getCurrentTrack(),e=t?.src||this.getAttribute("src")||"";this.audioEl.src!==e&&e&&(this.audioEl.src=e,this.audioEl.load()),this.updateTrackInfo(),this.updatePlaylistActive()}bindAudioEvents(){const t=this.audioEl;t&&(t.addEventListener("play",()=>{this.isPlaying=!0,this.dispatchEvent(new CustomEvent("db-play",{bubbles:!0,composed:!0})),this.updateUI()}),t.addEventListener("pause",()=>{this.isPlaying=!1,this.dispatchEvent(new CustomEvent("db-pause",{bubbles:!0,composed:!0})),this.updateUI()}),t.addEventListener("timeupdate",()=>{this.currentTime=t.currentTime,this.duration=t.duration||0,this.dispatchEvent(new CustomEvent("db-time-update",{detail:{currentTime:this.currentTime,duration:this.duration},bubbles:!0,composed:!0})),this.updateProgress(),this.updateTime()}),t.addEventListener("volumechange",()=>{this.volume=t.volume,this.isMuted=t.muted,this.dispatchEvent(new CustomEvent("db-volume-change",{detail:{volume:this.volume},bubbles:!0,composed:!0})),this.updateVolumeUI()}),t.addEventListener("ended",()=>this.handleEnded()),t.addEventListener("error",()=>{this.error="오디오를 재생할 수 없습니다.",this.dispatchEvent(new CustomEvent("db-error",{detail:{error:this.error},bubbles:!0,composed:!0})),this.updateUI()}),t.addEventListener("loadstart",()=>{this.isLoading=!0,this.error=null,this.updateUI()}),t.addEventListener("canplay",()=>{this.isLoading=!1,this.updateUI()}))}handleEnded(){this.isPlaying=!1,this.dispatchEvent(new CustomEvent("db-ended",{bubbles:!0,composed:!0}));const t=this.getPlaylist();if(t.length>0){if("one"===this.currentRepeatMode)this.audioEl&&(this.audioEl.currentTime=0,this.audioEl.play());else if("all"===this.currentRepeatMode||this.playlistIndex<t.length-1){const e=this.playlistIndex+1;e<t.length?this.setPlaylistIndex(e):"all"===this.currentRepeatMode&&this.setPlaylistIndex(0)}}else this.getBool("loop")&&this.audioEl&&(this.audioEl.currentTime=0,this.audioEl.play());this.updateUI()}setPlaylistIndex(t){this.playlistIndex=t,this.dispatchEvent(new CustomEvent("db-playlist-change",{detail:{index:t},bubbles:!0,composed:!0})),this.loadCurrentTrack(),this.audioEl&&this.audioEl.play()}togglePlay(){this.audioEl&&(this.isPlaying?this.audioEl.pause():this.audioEl.play())}toggleMute(){this.audioEl&&(this.audioEl.muted=!this.isMuted)}toggleRepeatMode(){const t=["none","one","all"],e=t[(t.indexOf(this.currentRepeatMode)+1)%t.length];this.currentRepeatMode=e,this.dispatchEvent(new CustomEvent("db-repeat-mode-change",{detail:{mode:e},bubbles:!0,composed:!0})),this.updateUI()}toggleShuffle(){this.isShuffle=!this.isShuffle,this.refreshShuffledPlaylist(),this.dispatchEvent(new CustomEvent("db-shuffle-change",{detail:{shuffle:this.isShuffle},bubbles:!0,composed:!0})),this.updateUI()}handlePlaybackRateChange(t){this.audioEl&&(this.audioEl.playbackRate=t,this.playbackRate=t,this.dispatchEvent(new CustomEvent("db-playback-rate-change",{detail:{rate:t},bubbles:!0,composed:!0})))}handleKeyDown(t){if(this.audioEl&&this.getBool("enable-keyboard",!0))switch(t.code){case"Space":t.preventDefault(),this.togglePlay();break;case"ArrowLeft":t.preventDefault(),this.audioEl.currentTime=Math.max(0,this.currentTime-10);break;case"ArrowRight":t.preventDefault(),this.audioEl.currentTime=Math.min(this.duration,this.currentTime+10);break;case"ArrowUp":t.preventDefault(),this.audioEl.volume=Math.min(1,this.volume+.1),this.audioEl.muted=!1;break;case"ArrowDown":t.preventDefault(),this.audioEl.volume=Math.max(0,this.volume-.1),this.audioEl.muted=0===this.audioEl.volume;break;case"KeyM":t.preventDefault(),this.toggleMute();break;case"KeyR":t.preventDefault(),this.toggleRepeatMode();break;case"KeyS":t.preventDefault(),this.toggleShuffle()}}iconButton(t,e,i,l){const a=document.createElement("button");return a.type="button",a.className=`${n}__control-button`,a.setAttribute("aria-label",e),a.append(s(t,this.getIconSize())),l?.disabled&&(a.disabled=!0),l?.data&&Object.entries(l.data).forEach(([t,e])=>a.setAttribute(`data-${t}`,e)),a.addEventListener("click",i),a}build(){const s=i(this.getAttribute("size"),l,"m"),o=i(this.getAttribute("variant"),a,"default"),r=this.getAttribute("theme")||"auto";this.className=[n,`${n}--size-${s}`,`${n}--variant-${o}`,`${n}--theme-${r}`].join(" "),this.audioEl=document.createElement("audio"),this.audioEl.preload="metadata",this.audioEl.hidden=!0;const h=document.createElement("div");h.className=`${n}__loading`;const u=document.createElement("div");u.className=`${n}__spinner`,h.append(u);const d=document.createElement("div");d.className=`${n}__error`;const c=document.createElement("p");c.textContent=t("오디오를 재생할 수 없습니다.");const m=document.createElement("button");m.type="button",m.textContent=t("다시 시도"),m.addEventListener("click",()=>window.location.reload()),d.append(c,m);const p=document.createElement("div");p.className=`${n}__album-art`,this.albumImgEl=document.createElement("img"),this.albumImgEl.addEventListener("error",()=>{this.albumImgEl&&(this.albumImgEl.style.display="none"),this.albumPlaceholderEl?.classList.remove("album-placeholder")}),this.albumPlaceholderEl=document.createElement("div"),this.albumPlaceholderEl.className="album-placeholder",this.albumPlaceholderEl.textContent="🎵",p.append(this.albumImgEl,this.albumPlaceholderEl);const E=document.createElement("div");E.className=`${n}__main`;const g=document.createElement("div");g.className=`${n}__info`,this.titleEl=document.createElement("div"),this.titleEl.className=`${n}__title`,this.artistEl=document.createElement("div"),this.artistEl.className=`${n}__artist`,this.albumEl=document.createElement("div"),this.albumEl.className=`${n}__album`,g.append(this.titleEl,this.artistEl,this.albumEl),this.progressEl=document.createElement("div"),this.progressEl.className=`${n}__progress`,this.progressBarEl=document.createElement("div"),this.progressBarEl.className=`${n}__progress-bar`,this.progressHandleEl=document.createElement("div"),this.progressHandleEl.className=`${n}__progress-handle`,this.progressEl.append(this.progressBarEl,this.progressHandleEl),this.progressEl.addEventListener("click",t=>{if(!this.audioEl||!this.progressEl)return;const e=this.progressEl.getBoundingClientRect(),s=(t.clientX-e.left)/e.width;this.audioEl.currentTime=s*(this.duration||0)});const b=document.createElement("div");b.className=`${n}__controls`;const y=document.createElement("div");y.className=`${n}__left-controls`;const v=document.createElement("div");v.className=`${n}__center-controls`;const f=document.createElement("div");f.className=`${n}__right-controls`,this.playButtonEl=document.createElement("button"),this.playButtonEl.type="button",this.playButtonEl.className=`${n}__play-button`,this.playButtonEl.addEventListener("click",()=>this.togglePlay()),v.append(this.playButtonEl),b.append(y,v,f);const _=document.createElement("div");_.className=`${n}__bottom-controls`;const k=document.createElement("div");k.className=`${n}__left-bottom`;const C=document.createElement("div");C.className=`${n}__right-bottom`;const I=document.createElement("div");I.className=`${n}__time`,this.timeCurrentEl=document.createElement("span");const $=document.createElement("span");$.textContent="/",this.timeDurationEl=document.createElement("span"),I.append(this.timeCurrentEl,$,this.timeDurationEl),k.append(I);const A=document.createElement("div");A.className=`${n}__volume`;const P=this.iconButton(this.isMuted||0===this.volume?"mute-filled":"volume-up",this.isMuted||0===this.volume?e("media.unmute"):e("media.mute"),()=>this.toggleMute());P.title=this.isMuted||0===this.volume?"음소거 해제":"음소거";const x=document.createElement("div");x.className=`${n}__volume-slider`;const M=document.createElement("div");M.className="volume-label";const w=document.createElement("span");w.textContent="🔊";const B=document.createElement("span");M.append(w,B),this.volumeInputEl=document.createElement("input"),this.volumeInputEl.type="range",this.volumeInputEl.min="0",this.volumeInputEl.max="1",this.volumeInputEl.step="0.1",this.volumeInputEl.addEventListener("input",()=>{if(!this.audioEl||!this.volumeInputEl)return;const t=parseFloat(this.volumeInputEl.value);this.audioEl.volume=t,this.audioEl.muted=0===t}),x.append(M,this.volumeInputEl),A.append(P,x);const N=document.createElement("div");N.className=`${n}__settings`;const T=this.iconButton("settings",e("media.settings"),()=>{this.showSettingsMenu=!this.showSettingsMenu,this.updateSettingsMenu()});this.settingsMenuEl=document.createElement("div"),this.settingsMenuEl.className=`${n}__settings-menu`,this.settingsMenuEl.hidden=!0;const S=document.createElement("div");S.className=`${n}__setting-group`;const R=document.createElement("label");R.textContent=t("재생 속도");const L=document.createElement("select");L.addEventListener("change",()=>this.handlePlaybackRateChange(parseFloat(L.value))),S.append(R,L),this.settingsMenuEl.append(S),N.append(T,this.settingsMenuEl),C.append(A,N),_.append(k,C),E.append(g,this.progressEl,b,_);const D=document.createElement("div");D.className=`${n}__playlist`,D.hidden=!0;const U=document.createElement("h4");U.textContent=t("플레이리스트"),this.playlistTracksEl=document.createElement("div"),this.playlistTracksEl.className=`${n}__playlist-tracks`,D.append(U,this.playlistTracksEl),this.append(this.audioEl,h,d,p,E,D),this._leftControls=y,this._rightControls=f,this._controls=b,this._time=I,this._volumeWrap=A,this._settings=N,this._playlist=D,this._rateSelect=L,this._volumePercent=B,this._muteButton=P}get leftControls(){return this._leftControls}get rightControls(){return this._rightControls}get controlsEl(){return this._controls}get timeEl(){return this._time}get volumeWrap(){return this._volumeWrap}get settingsEl(){return this._settings}get playlistEl(){return this._playlist}get rateSelect(){return this._rateSelect}get volumePercent(){return this._volumePercent}get muteButton(){return this._muteButton}updateTrackInfo(){const t=this.getCurrentTrack();this.titleEl&&(this.titleEl.textContent=t?.title||"Unknown Track"),this.artistEl&&(this.artistEl.textContent=t?.artist||"Unknown Artist"),this.albumEl&&(t?.album?(this.albumEl.textContent=t.album,this.albumEl.hidden=!1):this.albumEl.hidden=!0),this.albumImgEl&&this.albumPlaceholderEl&&(t?.albumArt?(this.albumImgEl.src=t.albumArt,this.albumImgEl.alt=t.title||"Album Art",this.albumImgEl.style.display=""):(this.albumImgEl.removeAttribute("src"),this.albumImgEl.style.display="none"),this.albumPlaceholderEl.className="album-placeholder")}updateProgress(){const t=this.duration>0?this.currentTime/this.duration*100:0;this.progressBarEl&&(this.progressBarEl.style.width=`${t}%`),this.progressHandleEl&&(this.progressHandleEl.style.left=`${t}%`,this.progressHandleEl.style.transform=t<=0?"translate(0, -50%)":t>=100?"translate(-100%, -50%)":"translate(-50%, -50%)")}updateTime(){this.timeCurrentEl&&(this.timeCurrentEl.textContent=this.formatTime(this.currentTime)),this.timeDurationEl&&(this.timeDurationEl.textContent=this.formatTime(this.duration))}updateVolumeUI(){this.volumeInputEl&&(this.volumeInputEl.value=String(this.volume)),this.volumePercent&&(this.volumePercent.textContent=`${Math.round(100*this.volume)}%`),this.muteButton&&(this.muteButton.replaceChildren(s(this.isMuted||0===this.volume?"mute-filled":"volume-up",this.getIconSize())),this.muteButton.title=this.isMuted||0===this.volume?"음소거 해제":"음소거")}updateSettingsMenu(){this.settingsMenuEl&&(this.settingsMenuEl.hidden=!this.showSettingsMenu)}rebuildControlButtons(){const t=this.getPlaylist(),i=1.2*this.getIconSize();this.leftControls.replaceChildren(),this.rightControls.replaceChildren(),t.length>0&&this.leftControls.append(this.iconButton("refresh",e("media.shuffle"),()=>this.toggleShuffle(),{data:{shuffle:"true"}})),this.leftControls.append(this.iconButton("chevron-left",e("media.previous"),()=>{if(t.length>0){const t=this.playlistIndex-1;t>=0&&this.setPlaylistIndex(t)}else this.audioEl&&(this.audioEl.currentTime=0)},{disabled:t.length>0&&0===this.playlistIndex})),this.playButtonEl&&this.playButtonEl.replaceChildren(s(this.isPlaying?"pause":"play",i)),this.rightControls.append(this.iconButton("chevron-right",e("media.next"),()=>{if(t.length>0){const e=this.playlistIndex+1;e<t.length&&this.setPlaylistIndex(e)}},{disabled:t.length>0&&this.playlistIndex===t.length-1})),t.length>0&&this.rightControls.append(this.iconButton("repeat",e("media.repeat"),()=>this.toggleRepeatMode(),{data:{repeat:"true"}}))}rebuildPlaylist(){if(!this.playlistTracksEl)return;const t=this.getPlaylist(),e=i(this.getAttribute("variant"),a,"default");this.playlistEl.hidden=!("full"===e&&t.length>0),this.playlistTracksEl.replaceChildren(),t.forEach((t,e)=>{const s=document.createElement("div");if(s.className=`${n}__playlist-track${e===this.playlistIndex?` ${n}__playlist-track--active`:""}`,s.addEventListener("click",()=>this.setPlaylistIndex(e)),t.albumArt){const e=document.createElement("img");e.src=t.albumArt,e.alt=t.title||"Album Art",s.append(e)}const i=document.createElement("div");i.className=`${n}__playlist-track-info`;const l=document.createElement("div");l.className=`${n}__playlist-track-title`,l.textContent=t.title||"Unknown Track";const a=document.createElement("div");a.className=`${n}__playlist-track-artist`,a.textContent=t.artist||"Unknown Artist",i.append(l,a),s.append(i),this.playlistTracksEl?.append(s)})}updatePlaylistActive(){this.playlistTracksEl&&Array.from(this.playlistTracksEl.children).forEach((t,e)=>{t.classList.toggle(`${n}__playlist-track--active`,e===this.playlistIndex)})}updateUI(){const t=i(this.getAttribute("size"),l,"m"),e=i(this.getAttribute("variant"),a,"default"),s=this.getAttribute("theme")||"auto",o=this.getBool("show-controls",!0),r=this.getBool("show-progress",!0),h=this.getBool("show-time",!0),u=this.getBool("show-volume",!0),d=this.getBool("show-settings",!1);this.className=[n,`${n}--size-${t}`,`${n}--variant-${e}`,`${n}--theme-${s}`,this.isPlaying&&`${n}--playing`,!this.isPlaying&&`${n}--paused`,this.isMuted&&`${n}--muted`,this.isLoading&&`${n}--loading`,this.error&&`${n}--error`,this.isShuffle&&`${n}--shuffle`,`${n}--repeat-${this.currentRepeatMode}`].filter(Boolean).join(" "),this.audioEl&&(this.audioEl.autoplay=this.getBool("auto-play"),this.audioEl.loop=this.getBool("loop"),this.audioEl.muted=this.getBool("muted"),this.audioEl.playbackRate=this.playbackRate),this.progressEl&&(this.progressEl.hidden=!r),this.controlsEl&&(this.controlsEl.hidden=!o),this.timeEl&&(this.timeEl.hidden=!h),this.volumeWrap&&(this.volumeWrap.hidden=!u),this.settingsEl&&(this.settingsEl.hidden=!d),this.rateSelect.replaceChildren(...this.getPlaybackRates().map(t=>{const e=document.createElement("option");return e.value=String(t),e.textContent=`${t}x`,t===this.playbackRate&&(e.selected=!0),e})),this.rebuildControlButtons(),this.updateTrackInfo(),this.updateProgress(),this.updateTime(),this.updateVolumeUI(),this.updateSettingsMenu(),this.rebuildPlaylist()}}customElements.get("db-audio-player")||customElements.define("db-audio-player",DbAudioPlayer);export{DbAudioPlayer};
|
|
1
|
+
import{subscribeDbLocale as t,dbText as e,t as s}from"../core/i18n.js";import{createDbIcon as i}from"./db-icons.js";import{coerceToContract as l}from"../node_modules/@designbasekorea/ui-contracts/dist/primitives.js";import{AUDIO_PLAYER_SIZES as a,AUDIO_PLAYER_VARIANTS as n}from"../node_modules/@designbasekorea/ui-contracts/dist/components.js";const r="designbase-wc-audio-player";class DbAudioPlayer extends HTMLElement{constructor(){super(...arguments),this.built=!1,this.isPlaying=!1,this.isMuted=!1,this.volume=1,this.currentTime=0,this.duration=0,this.showSettingsMenu=!1,this.playbackRate=1,this.currentRepeatMode="none",this.isShuffle=!1,this.isLoading=!1,this.error=null,this.playlistIndex=0,this.shuffledPlaylist=[],this.boundKeyDown=t=>this.handleKeyDown(t)}static get observedAttributes(){return["src","title","artist","album","album-art","size","variant","theme","auto-play","loop","muted","show-controls","enable-keyboard","show-progress","show-time","show-volume","show-settings","playlist","current-index","playback-rates","default-playback-rate","repeat-mode","shuffle"]}connectedCallback(){this.style.display="block",this.built||(this.built=!0,this.build(),this.bindAudioEvents()),this.syncFromAttributes(),this.updateUI(),this.unsubscribeLocale?.(),this.unsubscribeLocale=t(()=>this.refreshLocale()),this.refreshLocale(),this.getBool("enable-keyboard",!0)&&document.addEventListener("keydown",this.boundKeyDown)}disconnectedCallback(){this.unsubscribeLocale?.(),this.unsubscribeLocale=void 0,document.removeEventListener("keydown",this.boundKeyDown)}attributeChangedCallback(t){if(this.built){if("current-index"===t)return this.playlistIndex=Number(this.getAttribute("current-index")||0),void this.loadCurrentTrack();"repeat-mode"===t&&(this.currentRepeatMode=this.getAttribute("repeat-mode")||"none"),"shuffle"===t&&(this.isShuffle=this.getBool("shuffle"),this.refreshShuffledPlaylist()),"default-playback-rate"===t&&(this.playbackRate=Number(this.getAttribute("default-playback-rate")||1),this.audioEl&&(this.audioEl.playbackRate=this.playbackRate)),this.updateUI()}}getBool(t,e=!1){if(!this.hasAttribute(t))return e;const s=this.getAttribute(t);return""===s||"true"===s}getPlaylist(){try{const t=this.getAttribute("playlist"),e=t?JSON.parse(t):[];return Array.isArray(e)?e:[]}catch{return[]}}getPlaybackRates(){try{const t=this.getAttribute("playback-rates"),e=t?JSON.parse(t):[.5,.75,1,1.25,1.5,2];return Array.isArray(e)&&e.length>0?e:[.5,.75,1,1.25,1.5,2]}catch{return[.5,.75,1,1.25,1.5,2]}}getIconSize(){const t=l(this.getAttribute("size"),a,"m");return"s"===t?16:"l"===t?24:"xl"===t?32:20}getCurrentTrack(){const t=this.getPlaylist();if(t.length>0){return t[this.isShuffle&&this.shuffledPlaylist.length>0?this.shuffledPlaylist[this.playlistIndex]??this.playlistIndex:this.playlistIndex]||t[0]}return{src:this.getAttribute("src")||"",title:this.getAttribute("title")||void 0,artist:this.getAttribute("artist")||void 0,album:this.getAttribute("album")||void 0,albumArt:this.getAttribute("album-art")||void 0}}refreshShuffledPlaylist(){const t=this.getPlaylist();if(t.length>0&&this.isShuffle){const e=Array.from({length:t.length},(t,e)=>e);this.shuffledPlaylist=[...e].sort(()=>Math.random()-.5)}else this.shuffledPlaylist=[]}formatTime(t){return`${Math.floor(t/60)}:${Math.floor(t%60).toString().padStart(2,"0")}`}syncFromAttributes(){this.playlistIndex=Number(this.getAttribute("current-index")||0),this.currentRepeatMode=this.getAttribute("repeat-mode")||"none",this.isShuffle=this.getBool("shuffle"),this.playbackRate=Number(this.getAttribute("default-playback-rate")||1),this.isMuted=this.getBool("muted"),this.volume=this.isMuted?0:1,this.refreshShuffledPlaylist(),this.loadCurrentTrack()}loadCurrentTrack(){if(!this.audioEl)return;const t=this.getCurrentTrack(),e=t?.src||this.getAttribute("src")||"";this.audioEl.src!==e&&e&&(this.audioEl.src=e,this.audioEl.load()),this.updateTrackInfo(),this.updatePlaylistActive()}bindAudioEvents(){const t=this.audioEl;t&&(t.addEventListener("play",()=>{this.isPlaying=!0,this.dispatchEvent(new CustomEvent("db-play",{bubbles:!0,composed:!0})),this.updateUI()}),t.addEventListener("pause",()=>{this.isPlaying=!1,this.dispatchEvent(new CustomEvent("db-pause",{bubbles:!0,composed:!0})),this.updateUI()}),t.addEventListener("timeupdate",()=>{this.currentTime=t.currentTime,this.duration=t.duration||0,this.dispatchEvent(new CustomEvent("db-time-update",{detail:{currentTime:this.currentTime,duration:this.duration},bubbles:!0,composed:!0})),this.updateProgress(),this.updateTime()}),t.addEventListener("volumechange",()=>{this.volume=t.volume,this.isMuted=t.muted,this.dispatchEvent(new CustomEvent("db-volume-change",{detail:{volume:this.volume},bubbles:!0,composed:!0})),this.updateVolumeUI()}),t.addEventListener("ended",()=>this.handleEnded()),t.addEventListener("error",()=>{this.error=e("오디오를 재생할 수 없습니다."),this.dispatchEvent(new CustomEvent("db-error",{detail:{error:this.error},bubbles:!0,composed:!0})),this.updateUI()}),t.addEventListener("loadstart",()=>{this.isLoading=!0,this.error=null,this.updateUI()}),t.addEventListener("canplay",()=>{this.isLoading=!1,this.updateUI()}))}handleEnded(){this.isPlaying=!1,this.dispatchEvent(new CustomEvent("db-ended",{bubbles:!0,composed:!0}));const t=this.getPlaylist();if(t.length>0){if("one"===this.currentRepeatMode)this.audioEl&&(this.audioEl.currentTime=0,this.audioEl.play());else if("all"===this.currentRepeatMode||this.playlistIndex<t.length-1){const e=this.playlistIndex+1;e<t.length?this.setPlaylistIndex(e):"all"===this.currentRepeatMode&&this.setPlaylistIndex(0)}}else this.getBool("loop")&&this.audioEl&&(this.audioEl.currentTime=0,this.audioEl.play());this.updateUI()}setPlaylistIndex(t){this.playlistIndex=t,this.dispatchEvent(new CustomEvent("db-playlist-change",{detail:{index:t},bubbles:!0,composed:!0})),this.loadCurrentTrack(),this.audioEl&&this.audioEl.play()}togglePlay(){this.audioEl&&(this.isPlaying?this.audioEl.pause():this.audioEl.play())}toggleMute(){this.audioEl&&(this.audioEl.muted=!this.isMuted)}toggleRepeatMode(){const t=["none","one","all"],e=t[(t.indexOf(this.currentRepeatMode)+1)%t.length];this.currentRepeatMode=e,this.dispatchEvent(new CustomEvent("db-repeat-mode-change",{detail:{mode:e},bubbles:!0,composed:!0})),this.updateUI()}toggleShuffle(){this.isShuffle=!this.isShuffle,this.refreshShuffledPlaylist(),this.dispatchEvent(new CustomEvent("db-shuffle-change",{detail:{shuffle:this.isShuffle},bubbles:!0,composed:!0})),this.updateUI()}handlePlaybackRateChange(t){this.audioEl&&(this.audioEl.playbackRate=t,this.playbackRate=t,this.dispatchEvent(new CustomEvent("db-playback-rate-change",{detail:{rate:t},bubbles:!0,composed:!0})))}handleKeyDown(t){if(this.audioEl&&this.getBool("enable-keyboard",!0))switch(t.code){case"Space":t.preventDefault(),this.togglePlay();break;case"ArrowLeft":t.preventDefault(),this.audioEl.currentTime=Math.max(0,this.currentTime-10);break;case"ArrowRight":t.preventDefault(),this.audioEl.currentTime=Math.min(this.duration,this.currentTime+10);break;case"ArrowUp":t.preventDefault(),this.audioEl.volume=Math.min(1,this.volume+.1),this.audioEl.muted=!1;break;case"ArrowDown":t.preventDefault(),this.audioEl.volume=Math.max(0,this.volume-.1),this.audioEl.muted=0===this.audioEl.volume;break;case"KeyM":t.preventDefault(),this.toggleMute();break;case"KeyR":t.preventDefault(),this.toggleRepeatMode();break;case"KeyS":t.preventDefault(),this.toggleShuffle()}}iconButton(t,e,s,l){const a=document.createElement("button");return a.type="button",a.className=`${r}__control-button`,a.setAttribute("aria-label",e),a.append(i(t,this.getIconSize())),l?.disabled&&(a.disabled=!0),l?.data&&Object.entries(l.data).forEach(([t,e])=>a.setAttribute(`data-${t}`,e)),a.addEventListener("click",s),a}build(){const t=l(this.getAttribute("size"),a,"m"),i=l(this.getAttribute("variant"),n,"default"),o=this.getAttribute("theme")||"auto";this.className=[r,`${r}--size-${t}`,`${r}--variant-${i}`,`${r}--theme-${o}`].join(" "),this.audioEl=document.createElement("audio"),this.audioEl.preload="metadata",this.audioEl.hidden=!0;const h=document.createElement("div");h.className=`${r}__loading`;const u=document.createElement("div");u.className=`${r}__spinner`,h.append(u);const d=document.createElement("div");d.className=`${r}__error`;const c=document.createElement("p");c.textContent=e("오디오를 재생할 수 없습니다.");const m=document.createElement("button");m.type="button",m.textContent=e("다시 시도"),m.addEventListener("click",()=>window.location.reload()),d.append(c,m);const p=document.createElement("div");p.className=`${r}__album-art`,this.albumImgEl=document.createElement("img"),this.albumImgEl.addEventListener("error",()=>{this.albumImgEl&&(this.albumImgEl.style.display="none"),this.albumPlaceholderEl?.classList.remove("album-placeholder")}),this.albumPlaceholderEl=document.createElement("div"),this.albumPlaceholderEl.className="album-placeholder",this.albumPlaceholderEl.textContent="🎵",p.append(this.albumImgEl,this.albumPlaceholderEl);const E=document.createElement("div");E.className=`${r}__main`;const b=document.createElement("div");b.className=`${r}__info`,this.titleEl=document.createElement("div"),this.titleEl.className=`${r}__title`,this.artistEl=document.createElement("div"),this.artistEl.className=`${r}__artist`,this.albumEl=document.createElement("div"),this.albumEl.className=`${r}__album`,b.append(this.titleEl,this.artistEl,this.albumEl),this.progressEl=document.createElement("div"),this.progressEl.className=`${r}__progress`,this.progressBarEl=document.createElement("div"),this.progressBarEl.className=`${r}__progress-bar`,this.progressHandleEl=document.createElement("div"),this.progressHandleEl.className=`${r}__progress-handle`,this.progressEl.append(this.progressBarEl,this.progressHandleEl),this.progressEl.addEventListener("click",t=>{if(!this.audioEl||!this.progressEl)return;const e=this.progressEl.getBoundingClientRect(),s=(t.clientX-e.left)/e.width;this.audioEl.currentTime=s*(this.duration||0)});const g=document.createElement("div");g.className=`${r}__controls`;const y=document.createElement("div");y.className=`${r}__left-controls`;const v=document.createElement("div");v.className=`${r}__center-controls`;const f=document.createElement("div");f.className=`${r}__right-controls`,this.playButtonEl=document.createElement("button"),this.playButtonEl.type="button",this.playButtonEl.className=`${r}__play-button`,this.playButtonEl.addEventListener("click",()=>this.togglePlay()),v.append(this.playButtonEl),g.append(y,v,f);const _=document.createElement("div");_.className=`${r}__bottom-controls`;const k=document.createElement("div");k.className=`${r}__left-bottom`;const C=document.createElement("div");C.className=`${r}__right-bottom`;const $=document.createElement("div");$.className=`${r}__time`,this.timeCurrentEl=document.createElement("span");const A=document.createElement("span");A.textContent="/",this.timeDurationEl=document.createElement("span"),$.append(this.timeCurrentEl,A,this.timeDurationEl),k.append($);const I=document.createElement("div");I.className=`${r}__volume`;const P=this.iconButton(this.isMuted||0===this.volume?"mute-filled":"volume-up",this.isMuted||0===this.volume?s("media.unmute"):s("media.mute"),()=>this.toggleMute());P.title=s(this.isMuted||0===this.volume?"media.unmute":"media.mute");const x=document.createElement("div");x.className=`${r}__volume-slider`;const M=document.createElement("div");M.className="volume-label";const B=document.createElement("span");B.textContent="🔊";const w=document.createElement("span");M.append(B,w),this.volumeInputEl=document.createElement("input"),this.volumeInputEl.type="range",this.volumeInputEl.min="0",this.volumeInputEl.max="1",this.volumeInputEl.step="0.1",this.volumeInputEl.addEventListener("input",()=>{if(!this.audioEl||!this.volumeInputEl)return;const t=parseFloat(this.volumeInputEl.value);this.audioEl.volume=t,this.audioEl.muted=0===t}),x.append(M,this.volumeInputEl),I.append(P,x);const S=document.createElement("div");S.className=`${r}__settings`;const N=this.iconButton("settings",s("media.settings"),()=>{this.showSettingsMenu=!this.showSettingsMenu,this.updateSettingsMenu()});this.settingsMenuEl=document.createElement("div"),this.settingsMenuEl.className=`${r}__settings-menu`,this.settingsMenuEl.hidden=!0;const T=document.createElement("div");T.className=`${r}__setting-group`;const L=document.createElement("label");L.textContent=e("재생 속도");const R=document.createElement("select");R.addEventListener("change",()=>this.handlePlaybackRateChange(parseFloat(R.value))),T.append(L,R),this.settingsMenuEl.append(T),S.append(N,this.settingsMenuEl),C.append(I,S),_.append(k,C),E.append(b,this.progressEl,g,_);const D=document.createElement("div");D.className=`${r}__playlist`,D.hidden=!0;const U=document.createElement("h4");U.textContent=e("플레이리스트"),this.playlistTracksEl=document.createElement("div"),this.playlistTracksEl.className=`${r}__playlist-tracks`,D.append(U,this.playlistTracksEl),this.append(this.audioEl,h,d,p,E,D),this._leftControls=y,this._rightControls=f,this._controls=g,this._time=$,this._volumeWrap=I,this._settings=S,this._playlist=D,this._rateSelect=R,this._volumePercent=w,this._muteButton=P}get leftControls(){return this._leftControls}get rightControls(){return this._rightControls}get controlsEl(){return this._controls}get timeEl(){return this._time}get volumeWrap(){return this._volumeWrap}get settingsEl(){return this._settings}get playlistEl(){return this._playlist}get rateSelect(){return this._rateSelect}get volumePercent(){return this._volumePercent}get muteButton(){return this._muteButton}updateTrackInfo(){const t=this.getCurrentTrack();this.titleEl&&(this.titleEl.textContent=t?.title||"Unknown Track"),this.artistEl&&(this.artistEl.textContent=t?.artist||"Unknown Artist"),this.albumEl&&(t?.album?(this.albumEl.textContent=t.album,this.albumEl.hidden=!1):this.albumEl.hidden=!0),this.albumImgEl&&this.albumPlaceholderEl&&(t?.albumArt?(this.albumImgEl.src=t.albumArt,this.albumImgEl.alt=t.title||"Album Art",this.albumImgEl.style.display=""):(this.albumImgEl.removeAttribute("src"),this.albumImgEl.style.display="none"),this.albumPlaceholderEl.className="album-placeholder")}updateProgress(){const t=this.duration>0?this.currentTime/this.duration*100:0;this.progressBarEl&&(this.progressBarEl.style.width=`${t}%`),this.progressHandleEl&&(this.progressHandleEl.style.left=`${t}%`,this.progressHandleEl.style.transform=t<=0?"translate(0, -50%)":t>=100?"translate(-100%, -50%)":"translate(-50%, -50%)")}updateTime(){this.timeCurrentEl&&(this.timeCurrentEl.textContent=this.formatTime(this.currentTime)),this.timeDurationEl&&(this.timeDurationEl.textContent=this.formatTime(this.duration))}updateVolumeUI(){this.volumeInputEl&&(this.volumeInputEl.value=String(this.volume)),this.volumePercent&&(this.volumePercent.textContent=`${Math.round(100*this.volume)}%`),this.muteButton&&(this.muteButton.replaceChildren(i(this.isMuted||0===this.volume?"mute-filled":"volume-up",this.getIconSize())),this.muteButton.title=s(this.isMuted||0===this.volume?"media.unmute":"media.mute"),this.muteButton.setAttribute("aria-label",this.muteButton.title))}updateSettingsMenu(){this.settingsMenuEl&&(this.settingsMenuEl.hidden=!this.showSettingsMenu)}rebuildControlButtons(){const t=this.getPlaylist(),e=1.2*this.getIconSize();this.leftControls.replaceChildren(),this.rightControls.replaceChildren(),t.length>0&&this.leftControls.append(this.iconButton("refresh",s("media.shuffle"),()=>this.toggleShuffle(),{data:{shuffle:"true"}})),this.leftControls.append(this.iconButton("chevron-left",s("media.previous"),()=>{if(t.length>0){const t=this.playlistIndex-1;t>=0&&this.setPlaylistIndex(t)}else this.audioEl&&(this.audioEl.currentTime=0)},{disabled:t.length>0&&0===this.playlistIndex})),this.playButtonEl&&(this.playButtonEl.setAttribute("aria-label",s(this.isPlaying?"media.pause":"media.play")),this.playButtonEl.replaceChildren(i(this.isPlaying?"pause":"play",e))),this.rightControls.append(this.iconButton("chevron-right",s("media.next"),()=>{if(t.length>0){const e=this.playlistIndex+1;e<t.length&&this.setPlaylistIndex(e)}},{disabled:t.length>0&&this.playlistIndex===t.length-1})),t.length>0&&this.rightControls.append(this.iconButton("repeat",s("media.repeat"),()=>this.toggleRepeatMode(),{data:{repeat:"true"}}))}rebuildPlaylist(){if(!this.playlistTracksEl)return;const t=this.getPlaylist(),e=l(this.getAttribute("variant"),n,"default");this.playlistEl.hidden=!("full"===e&&t.length>0),this.playlistTracksEl.replaceChildren(),t.forEach((t,e)=>{const s=document.createElement("div");if(s.className=`${r}__playlist-track${e===this.playlistIndex?` ${r}__playlist-track--active`:""}`,s.addEventListener("click",()=>this.setPlaylistIndex(e)),t.albumArt){const e=document.createElement("img");e.src=t.albumArt,e.alt=t.title||"Album Art",s.append(e)}const i=document.createElement("div");i.className=`${r}__playlist-track-info`;const l=document.createElement("div");l.className=`${r}__playlist-track-title`,l.textContent=t.title||"Unknown Track";const a=document.createElement("div");a.className=`${r}__playlist-track-artist`,a.textContent=t.artist||"Unknown Artist",i.append(l,a),s.append(i),this.playlistTracksEl?.append(s)})}updatePlaylistActive(){this.playlistTracksEl&&Array.from(this.playlistTracksEl.children).forEach((t,e)=>{t.classList.toggle(`${r}__playlist-track--active`,e===this.playlistIndex)})}refreshLocale(){const text=(t,e)=>{const s=this.querySelector(t);s&&(s.textContent=e)};text(`.${r}__error p`,e("오디오를 재생할 수 없습니다.")),text(`.${r}__error button`,e("다시 시도")),text(`.${r}__setting-group label`,e("재생 속도")),text(`.${r}__playlist h4`,e("플레이리스트")),this.querySelector(`.${r}__settings > button`)?.setAttribute("aria-label",s("media.settings")),this.leftControls.querySelector("[data-shuffle]")?.setAttribute("aria-label",s("media.shuffle")),this.leftControls.querySelector("button:not([data-shuffle])")?.setAttribute("aria-label",s("media.previous")),this.rightControls.querySelector("button:not([data-repeat])")?.setAttribute("aria-label",s("media.next")),this.rightControls.querySelector("[data-repeat]")?.setAttribute("aria-label",s("media.repeat")),this.playButtonEl?.setAttribute("aria-label",s(this.isPlaying?"media.pause":"media.play")),this.updateVolumeUI()}updateUI(){const t=l(this.getAttribute("size"),a,"m"),e=l(this.getAttribute("variant"),n,"default"),s=this.getAttribute("theme")||"auto",i=this.getBool("show-controls",!0),o=this.getBool("show-progress",!0),h=this.getBool("show-time",!0),u=this.getBool("show-volume",!0),d=this.getBool("show-settings",!1);this.className=[r,`${r}--size-${t}`,`${r}--variant-${e}`,`${r}--theme-${s}`,this.isPlaying&&`${r}--playing`,!this.isPlaying&&`${r}--paused`,this.isMuted&&`${r}--muted`,this.isLoading&&`${r}--loading`,this.error&&`${r}--error`,this.isShuffle&&`${r}--shuffle`,`${r}--repeat-${this.currentRepeatMode}`].filter(Boolean).join(" "),this.audioEl&&(this.audioEl.autoplay=this.getBool("auto-play"),this.audioEl.loop=this.getBool("loop"),this.audioEl.muted=this.getBool("muted"),this.audioEl.playbackRate=this.playbackRate),this.progressEl&&(this.progressEl.hidden=!o),this.controlsEl&&(this.controlsEl.hidden=!i),this.timeEl&&(this.timeEl.hidden=!h),this.volumeWrap&&(this.volumeWrap.hidden=!u),this.settingsEl&&(this.settingsEl.hidden=!d),this.rateSelect.replaceChildren(...this.getPlaybackRates().map(t=>{const e=document.createElement("option");return e.value=String(t),e.textContent=`${t}x`,t===this.playbackRate&&(e.selected=!0),e})),this.rebuildControlButtons(),this.updateTrackInfo(),this.updateProgress(),this.updateTime(),this.updateVolumeUI(),this.updateSettingsMenu(),this.rebuildPlaylist()}}customElements.get("db-audio-player")||customElements.define("db-audio-player",DbAudioPlayer);export{DbAudioPlayer};
|